about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/pydash
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/pydash')
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/__init__.py756
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/arrays.py2964
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/chaining/__init__.py8
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/chaining/all_funcs.py44
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/chaining/all_funcs.pyi3540
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/chaining/chaining.py264
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/collections.py2181
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/exceptions.py22
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/functions.py1461
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/helpers.py327
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/numerical.py1252
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/objects.py2633
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/predicates.py1664
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/py.typed0
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/strings.py2363
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/types.py30
-rw-r--r--.venv/lib/python3.12/site-packages/pydash/utilities.py1511
17 files changed, 21020 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pydash/__init__.py b/.venv/lib/python3.12/site-packages/pydash/__init__.py
new file mode 100644
index 00000000..74eafd4f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/__init__.py
@@ -0,0 +1,756 @@
+"""Python port of Lo-Dash."""
+
+__version__ = "8.0.5"
+
+from .arrays import (
+    chunk,
+    compact,
+    concat,
+    difference,
+    difference_by,
+    difference_with,
+    drop,
+    drop_right,
+    drop_right_while,
+    drop_while,
+    duplicates,
+    fill,
+    find_index,
+    find_last_index,
+    flatten,
+    flatten_deep,
+    flatten_depth,
+    from_pairs,
+    head,
+    index_of,
+    initial,
+    intercalate,
+    interleave,
+    intersection,
+    intersection_by,
+    intersection_with,
+    intersperse,
+    last,
+    last_index_of,
+    mapcat,
+    nth,
+    pop,
+    pull,
+    pull_all,
+    pull_all_by,
+    pull_all_with,
+    pull_at,
+    push,
+    remove,
+    reverse,
+    shift,
+    slice_,
+    sort,
+    sorted_index,
+    sorted_index_by,
+    sorted_index_of,
+    sorted_last_index,
+    sorted_last_index_by,
+    sorted_last_index_of,
+    sorted_uniq,
+    sorted_uniq_by,
+    splice,
+    split_at,
+    tail,
+    take,
+    take_right,
+    take_right_while,
+    take_while,
+    union,
+    union_by,
+    union_with,
+    uniq,
+    uniq_by,
+    uniq_with,
+    unshift,
+    unzip,
+    unzip_with,
+    without,
+    xor,
+    xor_by,
+    xor_with,
+    zip_,
+    zip_object,
+    zip_object_deep,
+    zip_with,
+)
+from .chaining import _Dash, chain, tap
+from .collections import (
+    at,
+    count_by,
+    every,
+    filter_,
+    find,
+    find_last,
+    flat_map,
+    flat_map_deep,
+    flat_map_depth,
+    for_each,
+    for_each_right,
+    group_by,
+    includes,
+    invoke_map,
+    key_by,
+    map_,
+    nest,
+    order_by,
+    partition,
+    pluck,
+    reduce_,
+    reduce_right,
+    reductions,
+    reductions_right,
+    reject,
+    sample,
+    sample_size,
+    shuffle,
+    size,
+    some,
+    sort_by,
+)
+from .exceptions import InvalidMethod
+from .functions import (
+    after,
+    ary,
+    before,
+    conjoin,
+    curry,
+    curry_right,
+    debounce,
+    delay,
+    disjoin,
+    flip,
+    flow,
+    flow_right,
+    iterated,
+    juxtapose,
+    negate,
+    once,
+    over_args,
+    partial,
+    partial_right,
+    rearg,
+    spread,
+    throttle,
+    unary,
+    wrap,
+)
+from .numerical import (
+    add,
+    ceil,
+    clamp,
+    divide,
+    floor,
+    max_,
+    max_by,
+    mean,
+    mean_by,
+    median,
+    min_,
+    min_by,
+    moving_mean,
+    multiply,
+    power,
+    round_,
+    scale,
+    slope,
+    std_deviation,
+    subtract,
+    sum_,
+    sum_by,
+    transpose,
+    variance,
+    zscore,
+)
+from .objects import (
+    apply,
+    apply_catch,
+    apply_if,
+    apply_if_not_none,
+    assign,
+    assign_with,
+    callables,
+    clone,
+    clone_deep,
+    clone_deep_with,
+    clone_with,
+    defaults,
+    defaults_deep,
+    find_key,
+    find_last_key,
+    for_in,
+    for_in_right,
+    get,
+    has,
+    invert,
+    invert_by,
+    invoke,
+    keys,
+    map_keys,
+    map_values,
+    map_values_deep,
+    merge,
+    merge_with,
+    omit,
+    omit_by,
+    parse_int,
+    pick,
+    pick_by,
+    rename_keys,
+    set_,
+    set_with,
+    to_boolean,
+    to_dict,
+    to_integer,
+    to_list,
+    to_number,
+    to_pairs,
+    to_string,
+    transform,
+    unset,
+    update,
+    update_with,
+    values,
+)
+from .predicates import (
+    eq,
+    eq_cmp,
+    gt,
+    gt_cmp,
+    gte,
+    gte_cmp,
+    in_range,
+    in_range_cmp,
+    is_associative,
+    is_blank,
+    is_boolean,
+    is_builtin,
+    is_date,
+    is_decreasing,
+    is_dict,
+    is_empty,
+    is_equal,
+    is_equal_cmp,
+    is_equal_with,
+    is_equal_with_cmp,
+    is_error,
+    is_even,
+    is_float,
+    is_function,
+    is_increasing,
+    is_indexed,
+    is_instance_of,
+    is_instance_of_cmp,
+    is_integer,
+    is_iterable,
+    is_json,
+    is_list,
+    is_match,
+    is_match_cmp,
+    is_match_with,
+    is_match_with_cmp,
+    is_monotone,
+    is_monotone_cmp,
+    is_nan,
+    is_negative,
+    is_none,
+    is_number,
+    is_object,
+    is_odd,
+    is_positive,
+    is_reg_exp,
+    is_set,
+    is_strictly_decreasing,
+    is_strictly_increasing,
+    is_string,
+    is_tuple,
+    is_zero,
+    lt,
+    lt_cmp,
+    lte,
+    lte_cmp,
+)
+from .strings import (
+    camel_case,
+    capitalize,
+    chars,
+    chop,
+    chop_right,
+    clean,
+    count_substr,
+    deburr,
+    decapitalize,
+    ends_with,
+    ensure_ends_with,
+    ensure_starts_with,
+    escape,
+    escape_reg_exp,
+    has_substr,
+    human_case,
+    insert_substr,
+    join,
+    kebab_case,
+    lines,
+    lower_case,
+    lower_first,
+    number_format,
+    pad,
+    pad_end,
+    pad_start,
+    pascal_case,
+    predecessor,
+    prune,
+    quote,
+    reg_exp_js_match,
+    reg_exp_js_replace,
+    reg_exp_replace,
+    repeat,
+    replace,
+    replace_end,
+    replace_start,
+    separator_case,
+    series_phrase,
+    series_phrase_serial,
+    slugify,
+    snake_case,
+    split,
+    start_case,
+    starts_with,
+    strip_tags,
+    substr_left,
+    substr_left_end,
+    substr_right,
+    substr_right_end,
+    successor,
+    surround,
+    swap_case,
+    title_case,
+    to_lower,
+    to_upper,
+    trim,
+    trim_end,
+    trim_start,
+    truncate,
+    unescape,
+    unquote,
+    upper_case,
+    upper_first,
+    url,
+    words,
+)
+from .utilities import (
+    attempt,
+    cond,
+    conforms,
+    conforms_to,
+    constant,
+    default_to,
+    default_to_any,
+    identity,
+    iteratee,
+    matches,
+    matches_property,
+    memoize,
+    method,
+    method_of,
+    noop,
+    now,
+    nth_arg,
+    over,
+    over_every,
+    over_some,
+    properties,
+    property_,
+    property_of,
+    random,
+    range_,
+    range_right,
+    result,
+    retry,
+    stub_dict,
+    stub_false,
+    stub_list,
+    stub_string,
+    stub_true,
+    times,
+    to_path,
+    unique_id,
+)
+
+
+py_ = _Dash()
+_ = py_
+
+
+__all__ = (
+    "chunk",
+    "compact",
+    "concat",
+    "difference",
+    "difference_by",
+    "difference_with",
+    "drop",
+    "drop_right",
+    "drop_right_while",
+    "drop_while",
+    "duplicates",
+    "fill",
+    "find_index",
+    "find_last_index",
+    "flatten",
+    "flatten_deep",
+    "flatten_depth",
+    "from_pairs",
+    "head",
+    "index_of",
+    "initial",
+    "intercalate",
+    "interleave",
+    "intersection",
+    "intersection_by",
+    "intersection_with",
+    "intersperse",
+    "last",
+    "last_index_of",
+    "mapcat",
+    "nth",
+    "pop",
+    "pull",
+    "pull_all",
+    "pull_all_by",
+    "pull_all_with",
+    "pull_at",
+    "push",
+    "remove",
+    "reverse",
+    "shift",
+    "slice_",
+    "sort",
+    "sorted_index",
+    "sorted_index_by",
+    "sorted_index_of",
+    "sorted_last_index",
+    "sorted_last_index_by",
+    "sorted_last_index_of",
+    "sorted_uniq",
+    "sorted_uniq_by",
+    "splice",
+    "split_at",
+    "tail",
+    "take",
+    "take_right",
+    "take_right_while",
+    "take_while",
+    "union",
+    "union_by",
+    "union_with",
+    "uniq",
+    "uniq_by",
+    "uniq_with",
+    "unshift",
+    "unzip",
+    "unzip_with",
+    "without",
+    "xor",
+    "xor_by",
+    "xor_with",
+    "zip_",
+    "zip_object",
+    "zip_object_deep",
+    "zip_with",
+    "_Dash",
+    "chain",
+    "tap",
+    "at",
+    "count_by",
+    "every",
+    "filter_",
+    "find",
+    "find_last",
+    "flat_map",
+    "flat_map_deep",
+    "flat_map_depth",
+    "for_each",
+    "for_each_right",
+    "group_by",
+    "includes",
+    "invoke_map",
+    "key_by",
+    "map_",
+    "nest",
+    "order_by",
+    "partition",
+    "pluck",
+    "reduce_",
+    "reduce_right",
+    "reductions",
+    "reductions_right",
+    "reject",
+    "sample",
+    "sample_size",
+    "shuffle",
+    "size",
+    "some",
+    "sort_by",
+    "InvalidMethod",
+    "after",
+    "ary",
+    "before",
+    "conjoin",
+    "curry",
+    "curry_right",
+    "debounce",
+    "delay",
+    "disjoin",
+    "flip",
+    "flow",
+    "flow_right",
+    "iterated",
+    "juxtapose",
+    "negate",
+    "once",
+    "over_args",
+    "partial",
+    "partial_right",
+    "rearg",
+    "spread",
+    "throttle",
+    "unary",
+    "wrap",
+    "add",
+    "ceil",
+    "clamp",
+    "divide",
+    "floor",
+    "max_",
+    "max_by",
+    "mean",
+    "mean_by",
+    "median",
+    "min_",
+    "min_by",
+    "moving_mean",
+    "multiply",
+    "power",
+    "round_",
+    "scale",
+    "slope",
+    "std_deviation",
+    "subtract",
+    "sum_",
+    "sum_by",
+    "transpose",
+    "variance",
+    "zscore",
+    "apply",
+    "apply_catch",
+    "apply_if",
+    "apply_if_not_none",
+    "assign",
+    "assign_with",
+    "callables",
+    "clone",
+    "clone_deep",
+    "clone_deep_with",
+    "clone_with",
+    "defaults",
+    "defaults_deep",
+    "find_key",
+    "find_last_key",
+    "for_in",
+    "for_in_right",
+    "get",
+    "has",
+    "invert",
+    "invert_by",
+    "invoke",
+    "keys",
+    "map_keys",
+    "map_values",
+    "map_values_deep",
+    "merge",
+    "merge_with",
+    "omit",
+    "omit_by",
+    "parse_int",
+    "pick",
+    "pick_by",
+    "rename_keys",
+    "set_",
+    "set_with",
+    "to_boolean",
+    "to_dict",
+    "to_integer",
+    "to_list",
+    "to_number",
+    "to_pairs",
+    "to_string",
+    "transform",
+    "unset",
+    "update",
+    "update_with",
+    "values",
+    "eq",
+    "eq_cmp",
+    "gt",
+    "gt_cmp",
+    "gte",
+    "gte_cmp",
+    "in_range",
+    "in_range_cmp",
+    "is_associative",
+    "is_blank",
+    "is_boolean",
+    "is_builtin",
+    "is_date",
+    "is_decreasing",
+    "is_dict",
+    "is_empty",
+    "is_equal",
+    "is_equal_cmp",
+    "is_equal_with",
+    "is_equal_with_cmp",
+    "is_error",
+    "is_even",
+    "is_float",
+    "is_function",
+    "is_increasing",
+    "is_indexed",
+    "is_instance_of",
+    "is_instance_of_cmp",
+    "is_integer",
+    "is_iterable",
+    "is_json",
+    "is_list",
+    "is_match",
+    "is_match_cmp",
+    "is_match_with",
+    "is_match_with_cmp",
+    "is_monotone",
+    "is_monotone_cmp",
+    "is_nan",
+    "is_negative",
+    "is_none",
+    "is_number",
+    "is_object",
+    "is_odd",
+    "is_positive",
+    "is_reg_exp",
+    "is_set",
+    "is_strictly_decreasing",
+    "is_strictly_increasing",
+    "is_string",
+    "is_tuple",
+    "is_zero",
+    "lt",
+    "lt_cmp",
+    "lte",
+    "lte_cmp",
+    "camel_case",
+    "capitalize",
+    "chars",
+    "chop",
+    "chop_right",
+    "clean",
+    "count_substr",
+    "deburr",
+    "decapitalize",
+    "ends_with",
+    "ensure_ends_with",
+    "ensure_starts_with",
+    "escape",
+    "escape_reg_exp",
+    "has_substr",
+    "human_case",
+    "insert_substr",
+    "join",
+    "kebab_case",
+    "lines",
+    "lower_case",
+    "lower_first",
+    "number_format",
+    "pad",
+    "pad_end",
+    "pad_start",
+    "pascal_case",
+    "predecessor",
+    "prune",
+    "quote",
+    "reg_exp_js_match",
+    "reg_exp_js_replace",
+    "reg_exp_replace",
+    "repeat",
+    "replace",
+    "replace_end",
+    "replace_start",
+    "separator_case",
+    "series_phrase",
+    "series_phrase_serial",
+    "slugify",
+    "snake_case",
+    "split",
+    "start_case",
+    "starts_with",
+    "strip_tags",
+    "substr_left",
+    "substr_left_end",
+    "substr_right",
+    "substr_right_end",
+    "successor",
+    "surround",
+    "swap_case",
+    "title_case",
+    "to_lower",
+    "to_upper",
+    "trim",
+    "trim_end",
+    "trim_start",
+    "truncate",
+    "unescape",
+    "unquote",
+    "upper_case",
+    "upper_first",
+    "url",
+    "words",
+    "attempt",
+    "cond",
+    "conforms",
+    "conforms_to",
+    "constant",
+    "default_to",
+    "default_to_any",
+    "identity",
+    "iteratee",
+    "matches",
+    "matches_property",
+    "memoize",
+    "method",
+    "method_of",
+    "noop",
+    "now",
+    "nth_arg",
+    "over",
+    "over_every",
+    "over_some",
+    "properties",
+    "property_",
+    "property_of",
+    "random",
+    "range_",
+    "range_right",
+    "result",
+    "retry",
+    "stub_dict",
+    "stub_false",
+    "stub_list",
+    "stub_string",
+    "stub_true",
+    "times",
+    "to_path",
+    "unique_id",
+)
diff --git a/.venv/lib/python3.12/site-packages/pydash/arrays.py b/.venv/lib/python3.12/site-packages/pydash/arrays.py
new file mode 100644
index 00000000..2bc3bc94
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/arrays.py
@@ -0,0 +1,2964 @@
+"""
+Functions that operate on lists.
+
+.. versionadded:: 1.0.0
+"""
+
+from __future__ import annotations
+
+from bisect import bisect_left, bisect_right
+from functools import cmp_to_key
+from math import ceil
+import typing as t
+
+import pydash as pyd
+
+from .helpers import base_get, iteriteratee, parse_iteratee
+from .types import IterateeObjT
+
+
+if t.TYPE_CHECKING:
+    from _typeshed import SupportsRichComparisonT  # pragma: no cover
+
+
+__all__ = (
+    "chunk",
+    "compact",
+    "concat",
+    "difference",
+    "difference_by",
+    "difference_with",
+    "drop",
+    "drop_right",
+    "drop_right_while",
+    "drop_while",
+    "duplicates",
+    "fill",
+    "find_index",
+    "find_last_index",
+    "flatten",
+    "flatten_deep",
+    "flatten_depth",
+    "from_pairs",
+    "head",
+    "index_of",
+    "initial",
+    "intercalate",
+    "interleave",
+    "intersection",
+    "intersection_by",
+    "intersection_with",
+    "intersperse",
+    "last",
+    "last_index_of",
+    "mapcat",
+    "nth",
+    "pull",
+    "pull_all",
+    "pull_all_by",
+    "pull_all_with",
+    "pull_at",
+    "push",
+    "remove",
+    "reverse",
+    "shift",
+    "slice_",
+    "sort",
+    "sorted_index",
+    "sorted_index_by",
+    "sorted_index_of",
+    "sorted_last_index",
+    "sorted_last_index_by",
+    "sorted_last_index_of",
+    "sorted_uniq",
+    "sorted_uniq_by",
+    "splice",
+    "split_at",
+    "tail",
+    "take",
+    "take_right",
+    "take_right_while",
+    "take_while",
+    "union",
+    "union_by",
+    "union_with",
+    "uniq",
+    "uniq_by",
+    "uniq_with",
+    "unshift",
+    "unzip",
+    "unzip_with",
+    "without",
+    "xor",
+    "xor_by",
+    "xor_with",
+    "zip_",
+    "zip_object",
+    "zip_object_deep",
+    "zip_with",
+)
+
+T = t.TypeVar("T")
+T2 = t.TypeVar("T2")
+T3 = t.TypeVar("T3")
+T4 = t.TypeVar("T4")
+T5 = t.TypeVar("T5")
+SequenceT = t.TypeVar("SequenceT", bound=t.Sequence[t.Any])
+MutableSequenceT = t.TypeVar("MutableSequenceT", bound=t.MutableSequence[t.Any])
+
+
+def chunk(array: t.Sequence[T], size: int = 1) -> t.List[t.Sequence[T]]:
+    """
+    Creates a list of elements split into groups the length of `size`. If `array` can't be split
+    evenly, the final chunk will be the remaining elements.
+
+    Args:
+        array: List to chunk.
+        size: Chunk size. Defaults to ``1``.
+
+    Returns:
+        New list containing chunks of `array`.
+
+    Example:
+
+        >>> chunk([1, 2, 3, 4, 5], 2)
+        [[1, 2], [3, 4], [5]]
+
+    .. versionadded:: 1.1.0
+    """
+    chunks = int(ceil(len(array) / float(size)))
+    return [array[i * size : (i + 1) * size] for i in range(chunks)]
+
+
+def compact(array: t.Iterable[t.Union[T, None]]) -> t.List[T]:
+    """
+    Creates a list with all falsey values of array removed.
+
+    Args:
+        array: List to compact.
+
+    Returns:
+        Compacted list.
+
+    Example:
+
+        >>> compact(["", 1, 0, True, False, None])
+        [1, True]
+
+    .. versionadded:: 1.0.0
+    """
+    return [item for item in array if item]
+
+
+def concat(*arrays: t.Iterable[T]) -> t.List[T]:
+    """
+    Concatenates zero or more lists into one.
+
+    Args:
+        arrays: Lists to concatenate.
+
+    Returns:
+        Concatenated list.
+
+    Example:
+
+        >>> concat([1, 2], [3, 4], [[5], [6]])
+        [1, 2, 3, 4, [5], [6]]
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``cat`` to ``concat``.
+    """
+    return flatten(arrays)
+
+
+def difference(array: t.Iterable[T], *others: t.Iterable[T]) -> t.List[T]:
+    """
+    Creates a list of list elements not present in others.
+
+    Args:
+        array: List to process.
+        others: Lists to check.
+
+    Returns:
+        Difference between `others`.
+
+    Example:
+
+        >>> difference([1, 2, 3], [1], [2])
+        [3]
+
+    .. versionadded:: 1.0.0
+    """
+    return difference_with(array, *others)
+
+
+@t.overload
+def difference_by(
+    array: t.Iterable[T],
+    *others: t.Iterable[T],
+    iteratee: t.Union[IterateeObjT, t.Callable[[T], t.Any], None],
+) -> t.List[T]: ...
+
+
+@t.overload
+def difference_by(
+    array: t.Iterable[T], *others: t.Union[IterateeObjT, t.Iterable[T], t.Callable[[T], t.Any]]
+) -> t.List[T]: ...
+
+
+def difference_by(array, *others, **kwargs):
+    """
+    This method is like :func:`difference` except that it accepts an iteratee which is invoked for
+    each element of each array to generate the criterion by which they're compared. The order and
+    references of result values are determined by `array`. The iteratee is invoked with one
+    argument: ``(value)``.
+
+    Args:
+        array: The array to find the difference of.
+        others: Lists to check for difference with `array`.
+
+    Keyword Args:
+        iteratee: Function to transform the elements of the arrays. Defaults to
+            :func:`.identity`.
+
+    Returns:
+        Difference between `others`.
+
+    Example:
+
+        >>> difference_by([1.2, 1.5, 1.7, 2.8], [0.9, 3.2], round)
+        [1.5, 1.7]
+
+    .. versionadded:: 4.0.0
+    """
+    array = array[:]
+
+    if not others:
+        return array
+
+    # Check if last other is a potential iteratee.
+    iteratee, others = parse_iteratee("iteratee", *others, **kwargs)
+
+    for other in others:
+        if not other:
+            continue
+        array = list(iterdifference(array, other, iteratee=iteratee))
+
+    return array
+
+
+@t.overload
+def difference_with(
+    array: t.Iterable[T],
+    *others: t.Iterable[T2],
+    comparator: t.Union[t.Callable[[T, T2], t.Any], None],
+) -> t.List[T]: ...
+
+
+@t.overload
+def difference_with(
+    array: t.Iterable[T], *others: t.Union[t.Iterable[T2], t.Callable[[T, T2], t.Any]]
+) -> t.List[T]: ...
+
+
+def difference_with(array, *others, **kwargs):
+    """
+    This method is like :func:`difference` except that it accepts a comparator which is invoked to
+    compare the elements of all arrays. The order and references of result values are determined by
+    the first array. The comparator is invoked with two arguments: ``(arr_val, oth_val)``.
+
+    Args:
+        array: The array to find the difference of.
+        others: Lists to check for difference with `array`.
+
+    Keyword Args:
+        comparator: Function to compare the elements of the arrays. Defaults to
+            :func:`.is_equal`.
+
+    Returns:
+        Difference between `others`.
+
+    Example:
+
+        >>> array = ["apple", "banana", "pear"]
+        >>> others = (["avocado", "pumpkin"], ["peach"])
+        >>> comparator = lambda a, b: a[0] == b[0]
+        >>> difference_with(array, *others, comparator=comparator)
+        ['banana']
+
+    .. versionadded:: 4.0.0
+    """
+    array = array[:]
+
+    if not others:
+        return array
+
+    comparator = kwargs.get("comparator")
+    last_other = others[-1]
+
+    # Check if last other is a comparator.
+    if comparator is None and (callable(last_other) or last_other is None):
+        comparator = last_other
+        others = others[:-1]
+
+    for other in others:
+        if not other:
+            continue
+        array = list(iterdifference(array, other, comparator=comparator))
+
+    return array
+
+
+def drop(array: t.Sequence[T], n: int = 1) -> t.List[T]:
+    """
+    Creates a slice of `array` with `n` elements dropped from the beginning.
+
+    Args:
+        array: List to process.
+        n: Number of elements to drop. Defaults to ``1``.
+
+    Returns:
+        Dropped list.
+
+    Example:
+
+        >>> drop([1, 2, 3, 4], 2)
+        [3, 4]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 1.1.0
+        Added ``n`` argument and removed as alias of :func:`rest`.
+
+    .. versionchanged:: 3.0.0
+        Made ``n`` default to ``1``.
+    """
+    return drop_while(array, lambda _, index: index < n)
+
+
+def drop_right(array: t.Sequence[T], n: int = 1) -> t.List[T]:
+    """
+    Creates a slice of `array` with `n` elements dropped from the end.
+
+    Args:
+        array: List to process.
+        n: Number of elements to drop. Defaults to ``1``.
+
+    Returns:
+        Dropped list.
+
+    Example:
+
+        >>> drop_right([1, 2, 3, 4], 2)
+        [1, 2]
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 3.0.0
+        Made ``n`` default to ``1``.
+    """
+    length = len(array)
+    return drop_right_while(array, lambda _, index: (length - index) <= n)
+
+
+@t.overload
+def drop_right_while(
+    array: t.Sequence[T], predicate: t.Callable[[T, int, t.List[T]], t.Any]
+) -> t.List[T]: ...
+
+
+@t.overload
+def drop_right_while(array: t.Sequence[T], predicate: t.Callable[[T, int], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def drop_right_while(array: t.Sequence[T], predicate: t.Callable[[T], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def drop_right_while(array: t.Sequence[T], predicate: None = None) -> t.List[T]: ...
+
+
+def drop_right_while(array, predicate=None):
+    """
+    Creates a slice of `array` excluding elements dropped from the end. Elements are dropped until
+    the `predicate` returns falsey. The `predicate` is invoked with three arguments: ``(value,
+    index, array)``.
+
+    Args:
+        array: List to process.
+        predicate: Predicate called per iteration
+
+    Returns:
+        Dropped list.
+
+    Example:
+
+        >>> drop_right_while([1, 2, 3, 4], lambda x: x >= 3)
+        [1, 2]
+
+    .. versionadded:: 1.1.0
+    """
+    n = len(array)
+    for is_true, _, _, _ in iteriteratee(array, predicate, reverse=True):
+        if is_true:
+            n -= 1
+        else:
+            break
+
+    return array[:n]
+
+
+@t.overload
+def drop_while(
+    array: t.Sequence[T], predicate: t.Callable[[T, int, t.List[T]], t.Any]
+) -> t.List[T]: ...
+
+
+@t.overload
+def drop_while(array: t.Sequence[T], predicate: t.Callable[[T, int], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def drop_while(array: t.Sequence[T], predicate: t.Callable[[T], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def drop_while(array: t.Sequence[T], predicate: None = None) -> t.List[T]: ...
+
+
+def drop_while(array, predicate=None):
+    """
+    Creates a slice of `array` excluding elements dropped from the beginning. Elements are dropped
+    until the `predicate` returns falsey. The `predicate` is invoked with three arguments: ``(value,
+    index, array)``.
+
+    Args:
+        array: List to process.
+        predicate: Predicate called per iteration
+
+    Returns:
+        Dropped list.
+
+    Example:
+
+        >>> drop_while([1, 2, 3, 4], lambda x: x < 3)
+        [3, 4]
+
+    .. versionadded:: 1.1.0
+    """
+    n = 0
+    for is_true, _, _, _ in iteriteratee(array, predicate):
+        if is_true:
+            n += 1
+        else:
+            break
+
+    return array[n:]
+
+
+def duplicates(
+    array: t.Sequence[T], iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None
+) -> t.List[T]:
+    """
+    Creates a unique list of duplicate values from `array`. If iteratee is passed, each element of
+    array is passed through an iteratee before duplicates are computed. The iteratee is invoked with
+    three arguments: ``(value, index, array)``. If an object path is passed for iteratee, the
+    created iteratee will return the path value of the given element. If an object is passed for
+    iteratee, the created filter style iteratee will return ``True`` for elements that have the
+    properties of the given object, else ``False``.
+
+    Args:
+        array: List to process.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        List of duplicates.
+
+    Example:
+
+        >>> duplicates([0, 1, 3, 2, 3, 1])
+        [3, 1]
+
+    .. versionadded:: 3.0.0
+    """
+    if iteratee:
+        cbk = pyd.iteratee(iteratee)
+        computed = [cbk(item) for item in array]
+    else:
+        computed = array  # type: ignore
+
+    # NOTE: Using array[i] instead of item since iteratee could have modified
+    # returned item values.
+    lst = uniq(array[i] for i, _ in iterduplicates(computed))
+
+    return lst
+
+
+def fill(
+    array: t.Sequence[T], value: T2, start: int = 0, end: t.Union[int, None] = None
+) -> t.List[t.Union[T, T2]]:
+    """
+    Fills elements of array with value from `start` up to, but not including, `end`.
+
+    Args:
+        array: List to fill.
+        value: Value to fill with.
+        start: Index to start filling. Defaults to ``0``.
+        end: Index to end filling. Defaults to ``len(array)``.
+
+    Returns:
+        Filled `array`.
+
+    Example:
+
+        >>> fill([1, 2, 3, 4, 5], 0)
+        [0, 0, 0, 0, 0]
+        >>> fill([1, 2, 3, 4, 5], 0, 1, 3)
+        [1, 0, 0, 4, 5]
+        >>> fill([1, 2, 3, 4, 5], 0, 0, 100)
+        [0, 0, 0, 0, 0]
+
+    Warning:
+        `array` is modified in place.
+
+    .. versionadded:: 3.1.0
+    """
+    if end is None:
+        end = len(array)
+    else:
+        end = min(end, len(array))
+
+    # Use this style of assignment so that `array` is mutated.
+    array[:] = array[:start] + [value] * len(array[start:end]) + array[end:]  # type: ignore
+    return array  # type: ignore
+
+
+@t.overload
+def find_index(array: t.Iterable[T], predicate: t.Callable[[T, int, t.List[T]], t.Any]) -> int: ...
+
+
+@t.overload
+def find_index(array: t.Iterable[T], predicate: t.Callable[[T, int], t.Any]) -> int: ...
+
+
+@t.overload
+def find_index(array: t.Iterable[T], predicate: t.Callable[[T], t.Any]) -> int: ...
+
+
+@t.overload
+def find_index(array: t.Iterable[t.Any], predicate: IterateeObjT) -> int: ...
+
+
+@t.overload
+def find_index(array: t.Iterable[t.Any], predicate: None = None) -> int: ...
+
+
+def find_index(array, predicate=None):
+    """
+    This method is similar to :func:`pydash.collections.find`, except that it returns the index of
+    the element that passes the predicate check, instead of the element itself.
+
+    Args:
+        array: List to process.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        Index of found item or ``-1`` if not found.
+
+    Example:
+
+        >>> find_index([1, 2, 3, 4], lambda x: x >= 3)
+        2
+        >>> find_index([1, 2, 3, 4], lambda x: x > 4)
+        -1
+
+    .. versionadded:: 1.0.0
+    """
+    search = (i for is_true, _, i, _ in iteriteratee(array, predicate) if is_true)
+    return next(search, -1)
+
+
+@t.overload
+def find_last_index(
+    array: t.Iterable[T], predicate: t.Callable[[T, int, t.List[T]], t.Any]
+) -> int: ...
+
+
+@t.overload
+def find_last_index(array: t.Iterable[T], predicate: t.Callable[[T, int], t.Any]) -> int: ...
+
+
+@t.overload
+def find_last_index(array: t.Iterable[T], predicate: t.Callable[[T], t.Any]) -> int: ...
+
+
+@t.overload
+def find_last_index(array: t.Iterable[t.Any], predicate: IterateeObjT) -> int: ...
+
+
+@t.overload
+def find_last_index(array: t.Iterable[t.Any], predicate: None = None) -> int: ...
+
+
+def find_last_index(array, predicate=None):
+    """
+    This method is similar to :func:`find_index`, except that it iterates over elements from right
+    to left.
+
+    Args:
+        array: List to process.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        Index of found item or ``-1`` if not found.
+
+    Example:
+
+        >>> find_last_index([1, 2, 3, 4], lambda x: x >= 3)
+        3
+        >>> find_last_index([1, 2, 3, 4], lambda x: x > 4)
+        -1
+
+    .. versionadded:: 1.0.0
+    """
+    search = (i for is_true, _, i, _ in iteriteratee(array, predicate, reverse=True) if is_true)
+    return next(search, -1)
+
+
+@t.overload
+def flatten(array: t.Iterable[t.Iterable[T]]) -> t.List[T]: ...
+
+
+@t.overload
+def flatten(array: t.Iterable[T]) -> t.List[T]: ...
+
+
+def flatten(array):
+    """
+    Flattens array a single level deep.
+
+    Args:
+        array: List to flatten.
+
+    Returns:
+        Flattened list.
+
+    Example:
+
+        >>> flatten([[1], [2, [3]], [[4]]])
+        [1, 2, [3], [4]]
+
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 2.0.0
+        Removed `callback` option. Added ``is_deep`` option. Made it shallow
+        by default.
+
+    .. versionchanged:: 4.0.0
+        Removed ``is_deep`` option. Use :func:`flatten_deep` instead.
+    """
+    return flatten_depth(array, depth=1)
+
+
+def flatten_deep(array: t.Iterable[t.Any]) -> t.List[t.Any]:
+    """
+    Flattens an array recursively.
+
+    Args:
+        array: List to flatten.
+
+    Returns:
+        Flattened list.
+
+    Example:
+
+        >>> flatten_deep([[1], [2, [3]], [[4]]])
+        [1, 2, 3, 4]
+
+    .. versionadded:: 2.0.0
+    """
+    return flatten_depth(array, depth=-1)
+
+
+def flatten_depth(array: t.Iterable[t.Any], depth: int = 1) -> t.List[t.Any]:
+    """
+    Recursively flatten `array` up to `depth` times.
+
+    Args:
+        array: List to flatten.
+        depth: Depth to flatten to. Defaults to ``1``.
+
+    Returns:
+        Flattened list.
+
+    Example:
+
+        >>> flatten_depth([[[1], [2, [3]], [[4]]]], 1)
+        [[1], [2, [3]], [[4]]]
+        >>> flatten_depth([[[1], [2, [3]], [[4]]]], 2)
+        [1, 2, [3], [4]]
+        >>> flatten_depth([[[1], [2, [3]], [[4]]]], 3)
+        [1, 2, 3, 4]
+        >>> flatten_depth([[[1], [2, [3]], [[4]]]], 4)
+        [1, 2, 3, 4]
+
+    .. versionadded:: 4.0.0
+    """
+    return list(iterflatten(array, depth=depth))
+
+
+@t.overload
+def from_pairs(pairs: t.Iterable[t.Tuple[T, T2]]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def from_pairs(
+    pairs: t.Iterable[t.List[t.Union[T, T2]]],
+) -> t.Dict[t.Union[T, T2], t.Union[T, T2]]: ...
+
+
+def from_pairs(pairs):
+    """
+    Returns a dict from the given list of pairs.
+
+    Args:
+        pairs: List of key-value pairs.
+
+    Returns:
+        dict
+
+    Example:
+
+        >>> from_pairs([["a", 1], ["b", 2]]) == {"a": 1, "b": 2}
+        True
+
+    .. versionadded:: 4.0.0
+    """
+    return dict(pairs)
+
+
+def head(array: t.Sequence[T]) -> t.Union[T, None]:
+    """
+    Return the first element of `array`.
+
+    Args:
+        array: List to process.
+
+    Returns:
+        First element of list.
+
+    Example:
+
+        >>> head([1, 2, 3, 4])
+        1
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged::
+        Renamed from ``first`` to ``head``.
+    """
+    return base_get(array, 0, default=None)
+
+
+def index_of(array: t.Sequence[T], value: T, from_index: int = 0) -> int:
+    """
+    Gets the index at which the first occurrence of value is found.
+
+    Args:
+        array: List to search.
+        value: Value to search for.
+        from_index: Index to search from.
+
+    Returns:
+        Index of found item or ``-1`` if not found.
+
+    Example:
+
+        >>> index_of([1, 2, 3, 4], 2)
+        1
+        >>> index_of([2, 1, 2, 3], 2, from_index=1)
+        2
+
+    .. versionadded:: 1.0.0
+    """
+    try:
+        return array.index(value, from_index)
+    except ValueError:
+        return -1
+
+
+def initial(array: t.Sequence[T]) -> t.Sequence[T]:
+    """
+    Return all but the last element of `array`.
+
+    Args:
+        array: List to process.
+
+    Returns:
+        Initial part of `array`.
+
+    Example:
+
+        >>> initial([1, 2, 3, 4])
+        [1, 2, 3]
+
+    .. versionadded:: 1.0.0
+    """
+    return array[:-1]
+
+
+@t.overload
+def intercalate(array: t.Iterable[t.Iterable[T]], separator: T2) -> t.List[t.Union[T, T2]]: ...
+
+
+@t.overload
+def intercalate(array: t.Iterable[T], separator: T2) -> t.List[t.Union[T, T2]]: ...
+
+
+def intercalate(array, separator):
+    """
+    Like :func:`intersperse` for lists of lists but shallowly flattening the result.
+
+    Args:
+        array: List to intercalate.
+        separator: Element to insert.
+
+    Returns:
+        Intercalated list.
+
+    Example:
+
+        >>> intercalate([1, [2], [3], 4], "x")
+        [1, 'x', 2, 'x', 3, 'x', 4]
+
+
+    .. versionadded:: 2.0.0
+    """
+    return flatten(intersperse(array, separator))
+
+
+def interleave(*arrays: t.Iterable[T]) -> t.List[T]:
+    """
+    Merge multiple lists into a single list by inserting the next element of each list by sequential
+    round-robin into the new list.
+
+    Args:
+        arrays: Lists to interleave.
+
+    Returns:
+        Interleaved list.
+
+    Example:
+
+        >>> interleave([1, 2, 3], [4, 5, 6], [7, 8, 9])
+        [1, 4, 7, 2, 5, 8, 3, 6, 9]
+
+    .. versionadded:: 2.0.0
+    """
+    return list(iterinterleave(*arrays))
+
+
+def intersection(array: t.Sequence[T], *others: t.Iterable[t.Any]) -> t.List[T]:
+    """
+    Computes the intersection of all the passed-in arrays.
+
+    Args:
+        array: The array to find the intersection of.
+        others: Lists to check for intersection with `array`.
+
+    Returns:
+        Intersection of provided lists.
+
+    Example:
+
+        >>> intersection([1, 2, 3], [1, 2, 3, 4, 5], [2, 3])
+        [2, 3]
+
+        >>> intersection([1, 2, 3])
+        [1, 2, 3]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Support finding intersection of unhashable types.
+    """
+    return intersection_with(array, *others)
+
+
+@t.overload
+def intersection_by(
+    array: t.Sequence[T],
+    *others: t.Iterable[t.Any],
+    iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT],
+) -> t.List[T]: ...
+
+
+@t.overload
+def intersection_by(
+    array: t.Sequence[T], *others: t.Union[t.Iterable[t.Any], t.Callable[[T], t.Any], IterateeObjT]
+) -> t.List[T]: ...
+
+
+def intersection_by(array, *others, **kwargs):
+    """
+    This method is like :func:`intersection` except that it accepts an iteratee which is invoked for
+    each element of each array to generate the criterion by which they're compared. The order and
+    references of result values are determined by `array`. The iteratee is invoked with one
+    argument: ``(value)``.
+
+    Args:
+        array: The array to find the intersection of.
+        others: Lists to check for intersection with `array`.
+
+    Keyword Args:
+        iteratee: Function to transform the elements of the arrays. Defaults to
+            :func:`.identity`.
+
+    Returns:
+        Intersection of provided lists.
+
+    Example:
+
+        >>> intersection_by([1.2, 1.5, 1.7, 2.8], [0.9, 3.2], round)
+        [1.2, 2.8]
+
+    .. versionadded:: 4.0.0
+    """
+    array = array[:]
+
+    if not others:
+        return array
+
+    iteratee, others = parse_iteratee("iteratee", *others, **kwargs)
+
+    # Sort by smallest list length to make intersection faster.
+    others = sorted(others, key=lambda other: len(other))
+
+    for other in others:
+        array = list(iterintersection(array, other, iteratee=iteratee))
+        if not array:
+            break
+
+    return array
+
+
+@t.overload
+def intersection_with(
+    array: t.Sequence[T], *others: t.Iterable[T2], comparator: t.Callable[[T, T2], t.Any]
+) -> t.List[T]: ...
+
+
+@t.overload
+def intersection_with(
+    array: t.Sequence[T], *others: t.Union[t.Iterable[T2], t.Callable[[T, T2], t.Any]]
+) -> t.List[T]: ...
+
+
+def intersection_with(array, *others, **kwargs):
+    """
+    This method is like :func:`intersection` except that it accepts a comparator which is invoked to
+    compare the elements of all arrays. The order and references of result values are determined by
+    the first array. The comparator is invoked with two arguments: ``(arr_val, oth_val)``.
+
+    Args:
+        array: The array to find the intersection of.
+        others: Lists to check for intersection with `array`.
+
+    Keyword Args:
+        comparator: Function to compare the elements of the arrays. Defaults to
+            :func:`.is_equal`.
+
+    Returns:
+        Intersection of provided lists.
+
+    Example:
+
+        >>> array = ["apple", "banana", "pear"]
+        >>> others = (["avocado", "pumpkin"], ["peach"])
+        >>> comparator = lambda a, b: a[0] == b[0]
+        >>> intersection_with(array, *others, comparator=comparator)
+        ['pear']
+
+    .. versionadded:: 4.0.0
+    """
+    array = array[:]
+
+    if not others:
+        return array
+
+    comparator, others = parse_iteratee("comparator", *others, **kwargs)
+
+    # Sort by smallest list length to reduce to intersection faster.
+    others = sorted(others, key=lambda other: len(other))
+
+    for other in others:
+        array = list(iterintersection(array, other, comparator=comparator))
+        if not array:
+            break
+
+    return array
+
+
+def intersperse(array: t.Iterable[T], separator: T2) -> t.List[t.Union[T, T2]]:
+    """
+    Insert a separating element between the elements of `array`.
+
+    Args:
+        array: List to intersperse.
+        separator: Element to insert.
+
+    Returns:
+        Interspersed list.
+
+    Example:
+
+        >>> intersperse([1, [2], [3], 4], "x")
+        [1, 'x', [2], 'x', [3], 'x', 4]
+
+    .. versionadded:: 2.0.0
+    """
+    return list(iterintersperse(array, separator))
+
+
+def last(array: t.Sequence[T]) -> t.Union[T, None]:
+    """
+    Return the last element of `array`.
+
+    Args:
+        array: List to process.
+
+    Returns:
+        Last part of `array`.
+
+    Example:
+
+        >>> last([1, 2, 3, 4])
+        4
+
+    .. versionadded:: 1.0.0
+    """
+    return base_get(array, -1, default=None)
+
+
+def last_index_of(
+    array: t.Sequence[t.Any], value: t.Any, from_index: t.Union[int, None] = None
+) -> int:
+    """
+    Gets the index at which the last occurrence of value is found.
+
+    Args:
+        array: List to search.
+        value: Value to search for.
+        from_index: Index to search from.
+
+    Returns:
+        Index of found item or ``-1`` if not found.
+
+    Example:
+
+        >>> last_index_of([1, 2, 2, 4], 2)
+        2
+        >>> last_index_of([1, 2, 2, 4], 2, from_index=1)
+        1
+
+    .. versionadded:: 1.0.0
+    """
+    index = array_len = len(array)
+
+    try:
+        # safe as we are catching any type errors
+        from_index = int(from_index)  # type: ignore
+    except (TypeError, ValueError):
+        pass
+    else:
+        # Set starting index base on from_index offset.
+        index = max(0, index + from_index) if from_index < 0 else min(from_index, index - 1)
+
+    while index:
+        if index < array_len and array[index] == value:
+            return index
+        index -= 1
+    return -1
+
+
+@t.overload
+def mapcat(
+    array: t.Iterable[T],
+    iteratee: t.Callable[[T, int, t.List[T]], t.Union[t.List[T2], t.List[t.List[T2]]]],
+) -> t.List[T2]: ...
+
+
+@t.overload
+def mapcat(array: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], T2]) -> t.List[T2]: ...
+
+
+@t.overload
+def mapcat(
+    array: t.Iterable[T], iteratee: t.Callable[[T, int], t.Union[t.List[T2], t.List[t.List[T2]]]]
+) -> t.List[T2]: ...
+
+
+@t.overload
+def mapcat(array: t.Iterable[T], iteratee: t.Callable[[T, int], T2]) -> t.List[T2]: ...
+
+
+@t.overload
+def mapcat(
+    array: t.Iterable[T], iteratee: t.Callable[[T], t.Union[t.List[T2], t.List[t.List[T2]]]]
+) -> t.List[T2]: ...
+
+
+@t.overload
+def mapcat(array: t.Iterable[T], iteratee: t.Callable[[T], T2]) -> t.List[T2]: ...
+
+
+@t.overload
+def mapcat(
+    array: t.Iterable[t.Union[t.List[T], t.List[t.List[T]]]], iteratee: None = None
+) -> t.List[t.Union[T, t.List[T]]]: ...
+
+
+def mapcat(array, iteratee=None):
+    """
+    Map an iteratee to each element of a list and concatenate the results into a single list using
+    :func:`concat`.
+
+    Args:
+        array: List to map and concatenate.
+        iteratee: Iteratee to apply to each element.
+
+    Returns:
+        Mapped and concatenated list.
+
+    Example:
+
+        >>> mapcat(range(4), lambda x: list(range(x)))
+        [0, 0, 1, 0, 1, 2]
+
+    .. versionadded:: 2.0.0
+    """
+    return concat(*pyd.map_(array, iteratee))
+
+
+def nth(array: t.Iterable[T], pos: int = 0) -> t.Union[T, None]:
+    """
+    Gets the element at index n of array.
+
+    Args:
+        array: List passed in by the user.
+        pos: Index of element to return.
+
+    Returns:
+        Returns the element at :attr:`pos`.
+
+    Example:
+
+        >>> nth([1, 2, 3], 0)
+        1
+        >>> nth([3, 4, 5, 6], 2)
+        5
+        >>> nth([11, 22, 33], -1)
+        33
+        >>> nth([11, 22, 33])
+        11
+
+    .. versionadded:: 4.0.0
+    """
+    return pyd.get(array, pos)
+
+
+def pop(array: t.List[T], index: int = -1) -> T:
+    """
+    Remove element of array at `index` and return element.
+
+    Args:
+        array: List to pop from.
+        index: Index to remove element from. Defaults to ``-1``.
+
+    Returns:
+        Value at `index`.
+
+    Warning:
+        `array` is modified in place.
+
+    Example:
+
+        >>> array = [1, 2, 3, 4]
+        >>> item = pop(array)
+        >>> item
+        4
+        >>> array
+        [1, 2, 3]
+        >>> item = pop(array, index=0)
+        >>> item
+        1
+        >>> array
+        [2, 3]
+
+    .. versionadded:: 2.2.0
+    """
+    return array.pop(index)
+
+
+def pull(array: t.List[T], *values: T) -> t.List[T]:
+    """
+    Removes all provided values from the given array.
+
+    Args:
+        array: List to pull from.
+        values: Values to remove.
+
+    Returns:
+        Modified `array`.
+
+    Warning:
+        `array` is modified in place.
+
+    Example:
+
+        >>> pull([1, 2, 2, 3, 3, 4], 2, 3)
+        [1, 4]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        :func:`pull` method now calls :func:`pull_all` method for the desired
+        functionality.
+    """
+    return pull_all(array, values)
+
+
+def pull_all(array: t.List[T], values: t.Iterable[T]) -> t.List[T]:
+    """
+    Removes all provided values from the given array.
+
+    Args:
+        array: Array to modify.
+        values: Values to remove.
+
+    Returns:
+        Modified `array`.
+
+    Example:
+
+        >>> pull_all([1, 2, 2, 3, 3, 4], [2, 3])
+        [1, 4]
+
+    .. versionadded:: 4.0.0
+    """
+    # Use this style of assignment so that `array` is mutated.
+    array[:] = without(array, *values)
+    return array
+
+
+def pull_all_by(
+    array: t.List[T],
+    values: t.Iterable[T],
+    iteratee: t.Union[IterateeObjT, t.Callable[[T], t.Any], None] = None,
+) -> t.List[T]:
+    """
+    This method is like :func:`pull_all` except that it accepts iteratee which is invoked for each
+    element of array and values to generate the criterion by which they're compared. The iteratee is
+    invoked with one argument: ``(value)``.
+
+    Args:
+        array: Array to modify.
+        values: Values to remove.
+        iteratee: Function to transform the elements of the arrays. Defaults to
+            :func:`.identity`.
+
+    Returns:
+        Modified `array`.
+
+    Example:
+
+        >>> array = [{"x": 1}, {"x": 2}, {"x": 3}, {"x": 1}]
+        >>> pull_all_by(array, [{"x": 1}, {"x": 3}], "x")
+        [{'x': 2}]
+
+    .. versionadded:: 4.0.0
+    """
+    values = difference(array, difference_by(array, values, iteratee=iteratee))
+    return pull_all(array, values)
+
+
+def pull_all_with(
+    array: t.List[T],
+    values: t.Iterable[T],
+    comparator: t.Union[t.Callable[[T, T], t.Any], None] = None,
+) -> t.List[T]:
+    """
+    This method is like :func:`pull_all` except that it accepts comparator which is invoked to
+    compare elements of array to values. The comparator is invoked with two arguments: ``(arr_val,
+    oth_val)``.
+
+    Args:
+        array: Array to modify.
+        values: Values to remove.
+        comparator: Function to compare the elements of the arrays. Defaults to
+            :func:`.is_equal`.
+
+    Returns:
+        Modified `array`.
+
+    Example:
+
+        >>> array = [{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 5, "y": 6}]
+        >>> res = pull_all_with(array, [{"x": 3, "y": 4}], lambda a, b: a == b)
+        >>> res == [{"x": 1, "y": 2}, {"x": 5, "y": 6}]
+        True
+        >>> array = [{"x": 1, "y": 2}, {"x": 3, "y": 4}, {"x": 5, "y": 6}]
+        >>> res = pull_all_with(array, [{"x": 3, "y": 4}], lambda a, b: a != b)
+        >>> res == [{"x": 3, "y": 4}]
+        True
+
+    .. versionadded:: 4.0.0
+    """
+    values = difference(array, difference_with(array, values, comparator=comparator))
+    return pull_all(array, values)
+
+
+def pull_at(array: t.List[T], *indexes: int) -> t.List[T]:
+    """
+    Removes elements from `array` corresponding to the specified indexes and returns a list of the
+    removed elements. Indexes may be specified as a list of indexes or as individual arguments.
+
+    Args:
+        array: List to pull from.
+        indexes: Indexes to pull.
+
+    Returns:
+        Modified `array`.
+
+    Warning:
+        `array` is modified in place.
+
+    Example:
+
+        >>> pull_at([1, 2, 3, 4], 0, 2)
+        [2, 4]
+
+    .. versionadded:: 1.1.0
+    """
+    flat_indexes = flatten(indexes)
+    for index in sorted(flat_indexes, reverse=True):
+        del array[index]
+
+    return array
+
+
+def push(array: t.List[T], *items: T2) -> t.List[t.Union[T, T2]]:
+    """
+    Push items onto the end of `array` and return modified `array`.
+
+    Args:
+        array: List to push to.
+        items: Items to append.
+
+    Returns:
+        Modified `array`.
+
+    Warning:
+        `array` is modified in place.
+
+    Example:
+
+        >>> array = [1, 2, 3]
+        >>> push(array, 4, 5, [6])
+        [1, 2, 3, 4, 5, [6]]
+
+    .. versionadded:: 2.2.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``append``.
+    """
+    for item in items:
+        array.append(item)  # type: ignore
+    return array  # type: ignore
+
+
+def remove(
+    array: t.List[T],
+    predicate: t.Union[
+        t.Callable[[T, int, t.List[T]], t.Any],
+        t.Callable[[T, int], t.Any],
+        t.Callable[[T], t.Any],
+        None,
+    ] = None,
+) -> t.List[T]:
+    """
+    Removes all elements from a list that the predicate returns truthy for and returns an array of
+    removed elements.
+
+    Args:
+        array: List to remove elements from.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        Removed elements of `array`.
+
+    Warning:
+        `array` is modified in place.
+
+    Example:
+
+        >>> array = [1, 2, 3, 4]
+        >>> items = remove(array, lambda x: x >= 3)
+        >>> items
+        [3, 4]
+        >>> array
+        [1, 2]
+
+    .. versionadded:: 1.0.0
+    """
+    removed = []
+    kept = []
+
+    for is_true, _, i, _ in iteriteratee(array, predicate):
+        if is_true:
+            removed.append(array[i])
+        else:
+            kept.append(array[i])
+
+    # Modify array in place.
+    array[:] = kept
+
+    return removed
+
+
+def reverse(array: SequenceT) -> SequenceT:
+    """
+    Return `array` in reverse order.
+
+    Args:
+        array: Object to process.
+
+    Returns:
+        Reverse of object.
+
+    Example:
+
+        >>> reverse([1, 2, 3, 4])
+        [4, 3, 2, 1]
+
+    .. versionadded:: 2.2.0
+    """
+    # NOTE: Using this method to reverse object since it works for both lists and strings.
+    return array[::-1]  # type: ignore
+
+
+def shift(array: t.List[T]) -> T:
+    """
+    Remove the first element of `array` and return it.
+
+    Args:
+        array: List to shift.
+
+    Returns:
+        First element of `array`.
+
+    Warning:
+        `array` is modified in place.
+
+    Example:
+
+        >>> array = [1, 2, 3, 4]
+        >>> item = shift(array)
+        >>> item
+        1
+        >>> array
+        [2, 3, 4]
+
+    .. versionadded:: 2.2.0
+    """
+    return pop(array, 0)
+
+
+def slice_(array: SequenceT, start: int = 0, end: t.Union[int, None] = None) -> SequenceT:
+    """
+    Slices `array` from the `start` index up to, but not including, the `end` index.
+
+    Args:
+        array: Array to slice.
+        start: Start index. Defaults to ``0``.
+        end: End index. Defaults to selecting the value at ``start`` index.
+
+    Returns:
+        Sliced list.
+
+    Example:
+
+        >>> slice_([1, 2, 3, 4])
+        [1]
+        >>> slice_([1, 2, 3, 4], 1)
+        [2]
+        >>> slice_([1, 2, 3, 4], 1, 3)
+        [2, 3]
+
+    .. versionadded:: 1.1.0
+    """
+    if end is None:
+        end = (start + 1) if start >= 0 else (len(array) + start + 1)
+
+    return array[start:end]  # type: ignore
+
+
+@t.overload
+def sort(
+    array: t.List["SupportsRichComparisonT"],
+    comparator: None = None,
+    key: None = None,
+    reverse: bool = False,
+) -> t.List["SupportsRichComparisonT"]: ...
+
+
+@t.overload
+def sort(
+    array: t.List[T], comparator: t.Callable[[T, T], int], *, reverse: bool = False
+) -> t.List[T]: ...
+
+
+@t.overload
+def sort(
+    array: t.List[T], *, key: t.Callable[[T], "SupportsRichComparisonT"], reverse: bool = False
+) -> t.List[T]: ...
+
+
+def sort(array, comparator=None, key=None, reverse=False):
+    """
+    Sort `array` using optional `comparator`, `key`, and `reverse` options and return sorted
+    `array`.
+
+    Note:
+        Python 3 removed the option to pass a custom comparator function and instead only allows a
+        key function. Therefore, if a comparator function is passed in, it will be converted to a
+        key function automatically using ``functools.cmp_to_key``.
+
+    Args:
+        array: List to sort.
+        comparator: A custom comparator function used to sort the list.
+            Function should accept two arguments and return a negative, zero, or position number
+            depending on whether the first argument is considered smaller than, equal to, or larger
+            than the second argument. Defaults to ``None``. This argument is mutually exclusive with
+            `key`.
+        key: A function of one argument used to extract a comparator key from each list element.
+            Defaults to ``None``. This argument is mutually exclusive with `comparator`.
+        reverse: Whether to reverse the sort. Defaults to ``False``.
+
+    Returns:
+        Sorted list.
+
+    Warning:
+        `array` is modified in place.
+
+    Example:
+
+        >>> sort([2, 1, 4, 3])
+        [1, 2, 3, 4]
+        >>> sort([2, 1, 4, 3], reverse=True)
+        [4, 3, 2, 1]
+        >>> results = sort([{'a': 2, 'b': 1},\
+                            {'a': 3, 'b': 2},\
+                            {'a': 0, 'b': 3}],\
+                           key=lambda item: item['a'])
+        >>> assert results == [{'a': 0, 'b': 3},\
+                               {'a': 2, 'b': 1},\
+                               {'a': 3, 'b': 2}]
+
+    .. versionadded:: 2.2.0
+    """
+    if comparator and key:
+        raise ValueError('The "comparator" and "key" arguments are mutually exclusive')
+
+    if comparator:
+        key = cmp_to_key(comparator)
+
+    array.sort(key=key, reverse=reverse)
+    return array
+
+
+def sorted_index(
+    array: t.Sequence["SupportsRichComparisonT"], value: "SupportsRichComparisonT"
+) -> int:
+    """
+    Uses a binary search to determine the lowest index at which `value` should be inserted into
+    `array` in order to maintain its sort order.
+
+    Args:
+        array: List to inspect.
+        value: Value to evaluate.
+
+    Returns:
+        Returns the index at which `value` should be inserted into `array`.
+
+    Example:
+
+        >>> sorted_index([1, 2, 2, 3, 4], 2)
+        1
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Move iteratee support to :func:`sorted_index_by`.
+    """
+    return sorted_index_by(array, value)
+
+
+@t.overload
+def sorted_index_by(
+    array: t.Sequence[T],
+    value: T,
+    iteratee: t.Union[IterateeObjT, t.Callable[[T], "SupportsRichComparisonT"]],
+) -> int: ...
+
+
+@t.overload
+def sorted_index_by(
+    array: t.Sequence["SupportsRichComparisonT"],
+    value: "SupportsRichComparisonT",
+    iteratee: None = None,
+) -> int: ...
+
+
+def sorted_index_by(array, value, iteratee=None):
+    """
+    This method is like :func:`sorted_index` except that it accepts iteratee which is invoked for
+    `value` and each element of `array` to compute their sort ranking. The iteratee is invoked with
+    one argument: ``(value)``.
+
+    Args:
+        array: List to inspect.
+        value: Value to evaluate.
+        iteratee: The iteratee invoked per element. Defaults to :func:`.identity`.
+
+    Returns:
+        Returns the index at which `value` should be inserted into `array`.
+
+    Example:
+
+        >>> array = [{"x": 4}, {"x": 5}]
+        >>> sorted_index_by(array, {"x": 4}, lambda o: o["x"])
+        0
+        >>> sorted_index_by(array, {"x": 4}, "x")
+        0
+
+    .. versionadded:: 4.0.0
+    """
+    if iteratee:
+        # Generate array of sorted keys computed using iteratee.
+        iteratee = pyd.iteratee(iteratee)
+        array = sorted(iteratee(item) for item in array)
+        value = iteratee(value)
+
+    return bisect_left(array, value)
+
+
+def sorted_index_of(
+    array: t.Sequence["SupportsRichComparisonT"], value: "SupportsRichComparisonT"
+) -> int:
+    """
+    Returns the index of the matched `value` from the sorted `array`, else ``-1``.
+
+    Args:
+        array: Array to inspect.
+        value: Value to search for.
+
+    Returns:
+        Returns the index of the first matched value, else ``-1``.
+
+    Example:
+
+        >>> sorted_index_of([3, 5, 7, 10], 3)
+        0
+        >>> sorted_index_of([10, 10, 5, 7, 3], 10)
+        -1
+
+    .. versionadded:: 4.0.0
+    """
+    index = sorted_index(array, value)
+
+    if index < len(array) and array[index] == value:
+        return index
+    else:
+        return -1
+
+
+def sorted_last_index(
+    array: t.Sequence["SupportsRichComparisonT"], value: "SupportsRichComparisonT"
+) -> int:
+    """
+    This method is like :func:`sorted_index` except that it returns the highest index at which
+    `value` should be inserted into `array` in order to maintain its sort order.
+
+    Args:
+        array: List to inspect.
+        value: Value to evaluate.
+
+    Returns:
+        Returns the index at which `value` should be inserted into `array`.
+
+    Example:
+
+        >>> sorted_last_index([1, 2, 2, 3, 4], 2)
+        3
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 4.0.0
+        Move iteratee support to :func:`sorted_last_index_by`.
+    """
+    return sorted_last_index_by(array, value)
+
+
+@t.overload
+def sorted_last_index_by(
+    array: t.Sequence[T],
+    value: T,
+    iteratee: t.Union[IterateeObjT, t.Callable[[T], "SupportsRichComparisonT"]],
+) -> int: ...
+
+
+@t.overload
+def sorted_last_index_by(
+    array: t.Sequence["SupportsRichComparisonT"],
+    value: "SupportsRichComparisonT",
+    iteratee: None = None,
+) -> int: ...
+
+
+def sorted_last_index_by(array, value, iteratee=None):
+    """
+    This method is like :func:`sorted_last_index` except that it accepts iteratee which is invoked
+    for `value` and each element of `array` to compute their sort ranking. The iteratee is invoked
+    with one argument: ``(value)``.
+
+    Args:
+        array: List to inspect.
+        value: Value to evaluate.
+        iteratee: The iteratee invoked per element. Defaults to :func:`.identity`.
+
+    Returns:
+        Returns the index at which `value` should be inserted into `array`.
+
+    Example:
+
+        >>> array = [{"x": 4}, {"x": 5}]
+        >>> sorted_last_index_by(array, {"x": 4}, lambda o: o["x"])
+        1
+        >>> sorted_last_index_by(array, {"x": 4}, "x")
+        1
+    """
+    if iteratee:
+        # Generate array of sorted keys computed using iteratee.
+        iteratee = pyd.iteratee(iteratee)
+        array = sorted(iteratee(item) for item in array)
+        value = iteratee(value)
+
+    return bisect_right(array, value)
+
+
+def sorted_last_index_of(
+    array: t.Sequence["SupportsRichComparisonT"], value: "SupportsRichComparisonT"
+) -> int:
+    """
+    This method is like :func:`last_index_of` except that it performs a binary search on a sorted
+    `array`.
+
+    Args:
+        array: Array to inspect.
+        value: Value to search for.
+
+    Returns:
+        Returns the index of the matched value, else ``-1``.
+
+    Example:
+
+        >>> sorted_last_index_of([4, 5, 5, 5, 6], 5)
+        3
+        >>> sorted_last_index_of([6, 5, 5, 5, 4], 6)
+        -1
+
+    .. versionadded:: 4.0.0
+    """
+    index = sorted_last_index(array, value) - 1
+
+    if index < len(array) and array[index] == value:
+        return index
+    else:
+        return -1
+
+
+def sorted_uniq(array: t.Iterable["SupportsRichComparisonT"]) -> t.List["SupportsRichComparisonT"]:
+    """
+    Return sorted array with unique elements.
+
+    Args:
+        array: List of values to be sorted.
+
+    Returns:
+        List of unique elements in a sorted fashion.
+
+    Example:
+
+        >>> sorted_uniq([4, 2, 2, 5])
+        [2, 4, 5]
+        >>> sorted_uniq([-2, -2, 4, 1])
+        [-2, 1, 4]
+
+    .. versionadded:: 4.0.0
+    """
+    return sorted(uniq(array))
+
+
+def sorted_uniq_by(
+    array: t.Iterable["SupportsRichComparisonT"],
+    iteratee: t.Union[
+        t.Callable[["SupportsRichComparisonT"], "SupportsRichComparisonT"], None
+    ] = None,
+) -> t.List["SupportsRichComparisonT"]:
+    """
+    This method is like :func:`sorted_uniq` except that it accepts iteratee which is invoked for
+    each element in array to generate the criterion by which uniqueness is computed. The order of
+    result values is determined by the order they occur in the array. The iteratee is invoked with
+    one argument: ``(value)``.
+
+    Args:
+        array: List of values to be sorted.
+        iteratee: Function to transform the elements of the arrays. Defaults to
+            :func:`.identity`.
+
+    Returns:
+        Unique list.
+
+    Example:
+
+        >>> sorted_uniq_by([3, 2, 1, 3, 2, 1], lambda val: val % 2)
+        [2, 3]
+
+    .. versionadded:: 4.0.0
+    """
+    return sorted(uniq_by(array, iteratee=iteratee))
+
+
+def splice(
+    array: MutableSequenceT, start: int, count: t.Union[int, None] = None, *items: t.Any
+) -> MutableSequenceT:
+    """
+    Modify the contents of `array` by inserting elements starting at index `start` and removing
+    `count` number of elements after.
+
+    Args:
+        array: List to splice.
+        start: Start to splice at.
+        count: Number of items to remove starting at `start`. If ``None`` then all
+            items after `start` are removed. Defaults to ``None``.
+        items: Elements to insert starting at `start`. Each item is inserted in the order
+            given.
+
+    Returns:
+        The removed elements of `array` or the spliced string.
+
+    Warning:
+        `array` is modified in place if ``list``.
+
+    Example:
+
+        >>> array = [1, 2, 3, 4]
+        >>> splice(array, 1)
+        [2, 3, 4]
+        >>> array
+        [1]
+        >>> array = [1, 2, 3, 4]
+        >>> splice(array, 1, 2)
+        [2, 3]
+        >>> array
+        [1, 4]
+        >>> array = [1, 2, 3, 4]
+        >>> splice(array, 1, 2, 0, 0)
+        [2, 3]
+        >>> array
+        [1, 0, 0, 4]
+
+    .. versionadded:: 2.2.0
+
+    .. versionchanged:: 3.0.0
+        Support string splicing.
+    """
+    if count is None:
+        count = len(array) - start
+
+    is_string = pyd.is_string(array)
+
+    if is_string:
+        # allow reassignment with different type
+        array = list(array)  # type: ignore
+
+    removed = array[start : start + count]
+    del array[start : start + count]
+
+    for item in reverse(items):
+        array.insert(start, item)
+
+    if is_string:
+        return "".join(array)  # type: ignore
+    else:
+        return removed  # type: ignore
+
+
+def split_at(array: t.Sequence[T], index: int) -> t.List[t.Sequence[T]]:
+    """
+    Returns a list of two lists composed of the split of `array` at `index`.
+
+    Args:
+        array: List to split.
+        index: Index to split at.
+
+    Returns:
+        Split list.
+
+    Example:
+
+        >>> split_at([1, 2, 3, 4], 2)
+        [[1, 2], [3, 4]]
+
+    .. versionadded:: 2.0.0
+    """
+    return [array[:index], array[index:]]
+
+
+def tail(array: t.Sequence[T]) -> t.Sequence[T]:
+    """
+    Return all but the first element of `array`.
+
+    Args:
+        array: List to process.
+
+    Returns:
+        Rest of the list.
+
+    Example:
+
+        >>> tail([1, 2, 3, 4])
+        [2, 3, 4]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``rest`` to ``tail``.
+    """
+    return array[1:]
+
+
+def take(array: t.Sequence[T], n: int = 1) -> t.Sequence[T]:
+    """
+    Creates a slice of `array` with `n` elements taken from the beginning.
+
+    Args:
+        array: List to process.
+        n: Number of elements to take. Defaults to ``1``.
+
+    Returns:
+        Taken list.
+
+    Example:
+
+        >>> take([1, 2, 3, 4], 2)
+        [1, 2]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 1.1.0
+        Added ``n`` argument and removed as alias of :func:`first`.
+
+    .. versionchanged:: 3.0.0
+        Made ``n`` default to ``1``.
+    """
+    return take_while(array, lambda _, index: index < n)
+
+
+def take_right(array: t.Sequence[T], n: int = 1) -> t.Sequence[T]:
+    """
+    Creates a slice of `array` with `n` elements taken from the end.
+
+    Args:
+        array: List to process.
+        n: Number of elements to take. Defaults to ``1``.
+
+    Returns:
+        Taken list.
+
+    Example:
+
+        >>> take_right([1, 2, 3, 4], 2)
+        [3, 4]
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 3.0.0
+        Made ``n`` default to ``1``.
+    """
+    length = len(array)
+    return take_right_while(array, lambda _, index: (length - index) <= n)
+
+
+@t.overload
+def take_right_while(
+    array: t.Sequence[T], predicate: t.Callable[[T, int, t.List[T]], t.Any]
+) -> t.Sequence[T]: ...
+
+
+@t.overload
+def take_right_while(
+    array: t.Sequence[T], predicate: t.Callable[[T, int], t.Any]
+) -> t.Sequence[T]: ...
+
+
+@t.overload
+def take_right_while(array: t.Sequence[T], predicate: t.Callable[[T], t.Any]) -> t.Sequence[T]: ...
+
+
+@t.overload
+def take_right_while(array: t.Sequence[T], predicate: None = None) -> t.Sequence[T]: ...
+
+
+def take_right_while(array, predicate=None):
+    """
+    Creates a slice of `array` with elements taken from the end. Elements are taken until the
+    `predicate` returns falsey. The `predicate` is invoked with three arguments: ``(value, index,
+    array)``.
+
+    Args:
+        array: List to process.
+        predicate: Predicate called per iteration
+
+    Returns:
+        Dropped list.
+
+    Example:
+
+        >>> take_right_while([1, 2, 3, 4], lambda x: x >= 3)
+        [3, 4]
+
+    .. versionadded:: 1.1.0
+    """
+    n = len(array)
+    for is_true, _, _, _ in iteriteratee(array, predicate, reverse=True):
+        if is_true:
+            n -= 1
+        else:
+            break
+
+    return array[n:]
+
+
+@t.overload
+def take_while(
+    array: t.Sequence[T], predicate: t.Callable[[T, int, t.List[T]], t.Any]
+) -> t.List[T]: ...
+
+
+@t.overload
+def take_while(array: t.Sequence[T], predicate: t.Callable[[T, int], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def take_while(array: t.Sequence[T], predicate: t.Callable[[T], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def take_while(array: t.Sequence[T], predicate: None = None) -> t.List[T]: ...
+
+
+def take_while(array, predicate=None):
+    """
+    Creates a slice of `array` with elements taken from the beginning. Elements are taken until the
+    `predicate` returns falsey. The `predicate` is invoked with three arguments: ``(value, index,
+    array)``.
+
+    Args:
+        array: List to process.
+        predicate: Predicate called per iteration
+
+    Returns:
+        Taken list.
+
+    Example:
+
+        >>> take_while([1, 2, 3, 4], lambda x: x < 3)
+        [1, 2]
+
+    .. versionadded:: 1.1.0
+    """
+    n = 0
+    for is_true, _, _, _ in iteriteratee(array, predicate):
+        if is_true:
+            n += 1
+        else:
+            break
+
+    return array[:n]
+
+
+@t.overload
+def union(array: t.Sequence[T]) -> t.List[T]: ...
+
+
+@t.overload
+def union(array: t.Sequence[T], *others: t.Sequence[T2]) -> t.List[t.Union[T, T2]]: ...
+
+
+def union(array, *others):
+    """
+    Computes the union of the passed-in arrays.
+
+    Args:
+        array: List to union with.
+        others: Lists to unionize with `array`.
+
+    Returns:
+        Unionized list.
+
+    Example:
+
+        >>> union([1, 2, 3], [2, 3, 4], [3, 4, 5])
+        [1, 2, 3, 4, 5]
+
+    .. versionadded:: 1.0.0
+    """
+    if not others:
+        return array[:]
+
+    return uniq(flatten([array] + list(others)))
+
+
+@t.overload
+def union_by(
+    array: t.Sequence[T], *others: t.Iterable[T], iteratee: t.Callable[[T], t.Any]
+) -> t.List[T]: ...
+
+
+@t.overload
+def union_by(
+    array: t.Sequence[T], *others: t.Union[t.Iterable[T], t.Callable[[T], t.Any]]
+) -> t.List[T]: ...
+
+
+def union_by(array, *others, **kwargs):
+    """
+    This method is similar to :func:`union` except that it accepts iteratee which is invoked for
+    each element of each array to generate the criterion by which uniqueness is computed.
+
+    Args:
+        array: List to unionize with.
+        others: Lists to unionize with `array`.
+
+    Keyword Args:
+        iteratee: Function to invoke on each element.
+
+    Returns:
+        Unionized list.
+
+    Example:
+
+        >>> union_by([1, 2, 3], [2, 3, 4], iteratee=lambda x: x % 2)
+        [1, 2]
+        >>> union_by([1, 2, 3], [2, 3, 4], iteratee=lambda x: x % 9)
+        [1, 2, 3, 4]
+
+    .. versionadded:: 4.0.0
+    """
+    if not others:
+        return array[:]
+
+    iteratee, others = parse_iteratee("iteratee", *others, **kwargs)
+
+    return uniq_by(flatten([array] + list(others)), iteratee=iteratee)
+
+
+@t.overload
+def union_with(
+    array: t.Sequence[T], *others: t.Iterable[T2], comparator: t.Callable[[T, T2], t.Any]
+) -> t.List[T]: ...
+
+
+@t.overload
+def union_with(
+    array: t.Sequence[T], *others: t.Union[t.Iterable[T2], t.Callable[[T, T2], t.Any]]
+) -> t.List[T]: ...
+
+
+def union_with(array, *others, **kwargs):
+    """
+    This method is like :func:`union` except that it accepts comparator which is invoked to compare
+    elements of arrays. Result values are chosen from the first array in which the value occurs.
+
+    Args:
+        array: List to unionize with.
+        others: Lists to unionize with `array`.
+
+    Keyword Args:
+        comparator: Function to compare the elements of the arrays. Defaults to
+            :func:`.is_equal`.
+
+    Returns:
+        Unionized list.
+
+    Example:
+
+        >>> comparator = lambda a, b: (a % 2) == (b % 2)
+        >>> union_with([1, 2, 3], [2, 3, 4], comparator=comparator)
+        [1, 2]
+        >>> union_with([1, 2, 3], [2, 3, 4])
+        [1, 2, 3, 4]
+
+    .. versionadded:: 4.0.0
+    """
+    if not others:
+        return array[:]
+
+    comparator, others = parse_iteratee("comparator", *others, **kwargs)
+
+    return uniq_with(flatten([array] + list(others)), comparator=comparator)
+
+
+def uniq(array: t.Iterable[T]) -> t.List[T]:
+    """
+    Creates a duplicate-value-free version of the array. If iteratee is passed, each element of
+    array is passed through an iteratee before uniqueness is computed. The iteratee is invoked with
+    three arguments: ``(value, index, array)``. If an object path is passed for iteratee, the
+    created iteratee will return the path value of the given element. If an object is passed for
+    iteratee, the created filter style iteratee will return ``True`` for elements that have the
+    properties of the given object, else ``False``.
+
+    Args:
+        array: List to process.
+
+    Returns:
+        Unique list.
+
+    Example:
+
+        >>> uniq([1, 2, 3, 1, 2, 3])
+        [1, 2, 3]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+
+        - Moved `iteratee` argument to :func:`uniq_by`.
+        - Removed alias ``unique``.
+    """
+    return uniq_by(array)
+
+
+def uniq_by(
+    array: t.Iterable[T], iteratee: t.Union[t.Callable[[T], t.Any], None] = None
+) -> t.List[T]:
+    """
+    This method is like :func:`uniq` except that it accepts iteratee which is invoked for each
+    element in array to generate the criterion by which uniqueness is computed. The order of result
+    values is determined by the order they occur in the array. The iteratee is invoked with one
+    argument: ``(value)``.
+
+    Args:
+        array: List to process.
+        iteratee: Function to transform the elements of the arrays. Defaults to
+            :func:`.identity`.
+
+    Returns:
+        Unique list.
+
+    Example:
+
+        >>> uniq_by([1, 2, 3, 1, 2, 3], lambda val: val % 2)
+        [1, 2]
+
+    .. versionadded:: 4.0.0
+    """
+    return list(iterunique(array, iteratee=iteratee))
+
+
+def uniq_with(
+    array: t.Sequence[T], comparator: t.Union[t.Callable[[T, T], t.Any], None] = None
+) -> t.List[T]:
+    """
+    This method is like :func:`uniq` except that it accepts comparator which is invoked to compare
+    elements of array. The order of result values is determined by the order they occur in the
+    array.The comparator is invoked with two arguments: ``(value, other)``.
+
+    Args:
+        array: List to process.
+        comparator: Function to compare the elements of the arrays. Defaults to
+            :func:`.is_equal`.
+
+    Returns:
+        Unique list.
+
+    Example:
+
+        >>> uniq_with([1, 2, 3, 4, 5], lambda a, b: (a % 2) == (b % 2))
+        [1, 2]
+
+    .. versionadded:: 4.0.0
+    """
+    return list(iterunique(array, comparator=comparator))
+
+
+def unshift(array: t.List[T], *items: T2) -> t.List[t.Union[T, T2]]:
+    """
+    Insert the given elements at the beginning of `array` and return the modified list.
+
+    Args:
+        array: List to modify.
+        items: Items to insert.
+
+    Returns:
+        Modified list.
+
+    Warning:
+        `array` is modified in place.
+
+    Example:
+
+        >>> array = [1, 2, 3, 4]
+        >>> unshift(array, -1, -2)
+        [-1, -2, 1, 2, 3, 4]
+        >>> array
+        [-1, -2, 1, 2, 3, 4]
+
+    .. versionadded:: 2.2.0
+    """
+    for item in reverse(items):
+        array.insert(0, item)  # type: ignore
+
+    return array  # type: ignore
+
+
+@t.overload
+def unzip(array: t.Iterable[t.Tuple[T, T2]]) -> t.List[t.Tuple[T, T2]]: ...
+
+
+@t.overload
+def unzip(array: t.Iterable[t.Tuple[T, T2, T3]]) -> t.List[t.Tuple[T, T2, T3]]: ...
+
+
+@t.overload
+def unzip(array: t.Iterable[t.Tuple[T, T2, T3, T4]]) -> t.List[t.Tuple[T, T2, T3, T4]]: ...
+
+
+@t.overload
+def unzip(array: t.Iterable[t.Tuple[T, T2, T3, T4, T5]]) -> t.List[t.Tuple[T, T2, T3, T4, T5]]: ...
+
+
+@t.overload
+def unzip(array: t.Iterable[t.Iterable[t.Any]]) -> t.List[t.Tuple[t.Any, ...]]: ...
+
+
+def unzip(array):
+    """
+    The inverse of :func:`zip_`, this method splits groups of elements into tuples composed of
+    elements from each group at their corresponding indexes.
+
+    Args:
+        array: List to process.
+
+    Returns:
+        Unzipped list.
+
+    Example:
+
+        >>> unzip([(1, 4, 7), (2, 5, 8), (3, 6, 9)])
+        [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 8.0.0
+        Support list of tuples instead.
+    """
+    return zip_(*array)
+
+
+@t.overload
+def unzip_with(
+    array: t.Iterable[t.Tuple[T, T2]],
+    iteratee: t.Union[
+        t.Callable[[t.Union[T, T2, T3], t.Union[T, T2], int], T3],
+        t.Callable[[t.Union[T, T2, T3], t.Union[T, T2]], T3],
+        t.Callable[[t.Union[T, T2, T3]], T3],
+    ],
+) -> t.List[T3]: ...
+
+
+@t.overload
+def unzip_with(
+    array: t.Iterable[t.Iterable[t.Any]],
+    iteratee: t.Union[
+        t.Callable[[t.Any, t.Any, int], T3],
+        t.Callable[[t.Any, t.Any], T3],
+        t.Callable[[t.Any], T3],
+    ],
+) -> t.List[T3]: ...
+
+
+@t.overload
+def unzip_with(
+    array: t.Iterable[t.Iterable[T]],
+    iteratee: None = None,
+) -> t.List[t.Tuple[T]]: ...
+
+
+def unzip_with(array, iteratee=None):
+    """
+    This method is like :func:`unzip` except that it accepts an iteratee to specify how regrouped
+    values should be combined. The iteratee is invoked with three arguments: ``(accumulator, value,
+    index)``.
+
+    Args:
+        array: List to process.
+        iteratee: Function to combine regrouped values.
+
+    Returns:
+        Unzipped list.
+
+    Example:
+
+        >>> from pydash import add
+        >>> unzip_with([(1, 10, 100), (2, 20, 200)], add)
+        [3, 30, 300]
+
+    .. versionadded:: 3.3.0
+    """
+    if not array:
+        return []
+
+    result = unzip(array)
+
+    if iteratee is None:
+        return result
+
+    def cbk(group):
+        return pyd.reduce_(group, iteratee)
+
+    return pyd.map_(result, cbk)
+
+
+def without(array: t.Iterable[T], *values: T) -> t.List[T]:
+    """
+    Creates an array with all occurrences of the passed values removed.
+
+    Args:
+        array: List to filter.
+        values: Values to remove.
+
+    Returns:
+        Filtered list.
+
+    Example:
+
+        >>> without([1, 2, 3, 2, 4, 4], 2, 4)
+        [1, 3]
+
+    .. versionadded:: 1.0.0
+    """
+    return [item for item in array if item not in values]
+
+
+def xor(array: t.Iterable[T], *lists: t.Iterable[T]) -> t.List[T]:
+    """
+    Creates a list that is the symmetric difference of the provided lists.
+
+    Args:
+        array: List to process.
+        *lists: Lists to xor with.
+
+    Returns:
+        XOR'd list.
+
+    Example:
+
+        >>> xor([1, 3, 4], [1, 2, 4], [2])
+        [3]
+
+    .. versionadded:: 1.0.0
+    """
+    return xor_by(array, *lists)
+
+
+@t.overload
+def xor_by(
+    array: t.Iterable[T],
+    *lists: t.Iterable[T],
+    iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT],
+) -> t.List[T]: ...
+
+
+@t.overload
+def xor_by(
+    array: t.Iterable[T], *lists: t.Union[t.Iterable[T], t.Callable[[T], t.Any]]
+) -> t.List[T]: ...
+
+
+def xor_by(array, *lists, **kwargs):
+    """
+    This method is like :func:`xor` except that it accepts iteratee which is invoked for each
+    element of each arras to generate the criterion by which they're compared. The order of result
+    values is determined by the order they occur in the arrays. The iteratee is invoked with one
+    argument: ``(value)``.
+
+    Args:
+        array: List to process.
+        *lists: Lists to xor with.
+
+    Keyword Args:
+        iteratee: Function to transform the elements of the arrays. Defaults to
+            :func:`.identity`.
+
+    Returns:
+        XOR'd list.
+
+    Example:
+
+        >>> xor_by([2.1, 1.2], [2.3, 3.4], round)
+        [1.2, 3.4]
+        >>> xor_by([{"x": 1}], [{"x": 2}, {"x": 1}], "x")
+        [{'x': 2}]
+
+    .. versionadded:: 4.0.0
+    """
+    if not lists:
+        return array[:]
+
+    iteratee, lists = parse_iteratee("iteratee", *lists, **kwargs)
+
+    return xor(
+        uniq(
+            difference_by(
+                array + lists[0],
+                intersection_by(array, lists[0], iteratee=iteratee),
+                iteratee=iteratee,
+            )
+        ),
+        *lists[1:],
+    )
+
+
+@t.overload
+def xor_with(
+    array: t.Sequence[T], *lists: t.Iterable[T2], comparator: t.Callable[[T, T2], t.Any]
+) -> t.List[T]: ...
+
+
+@t.overload
+def xor_with(
+    array: t.Sequence[T], *lists: t.Union[t.Iterable[T2], t.Callable[[T, T2], t.Any]]
+) -> t.List[T]: ...
+
+
+def xor_with(array, *lists, **kwargs):
+    """
+    This method is like :func:`xor` except that it accepts comparator which is invoked to compare
+    elements of arrays. The order of result values is determined by the order they occur in the
+    arrays. The comparator is invoked with two arguments: ``(arr_val, oth_val)``.
+
+    Args:
+        array: List to process.
+        *lists: Lists to xor with.
+
+    Keyword Args:
+        comparator: Function to compare the elements of the arrays. Defaults to
+            :func:`.is_equal`.
+
+    Returns:
+        XOR'd list.
+
+    Example:
+
+        >>> objects = [{"x": 1, "y": 2}, {"x": 2, "y": 1}]
+        >>> others = [{"x": 1, "y": 1}, {"x": 1, "y": 2}]
+        >>> expected = [{"y": 1, "x": 2}, {"y": 1, "x": 1}]
+        >>> xor_with(objects, others, lambda a, b: a == b) == expected
+        True
+
+    .. versionadded:: 4.0.0
+    """
+    if not lists:
+        return array[:]
+
+    comp, lists = parse_iteratee("comparator", *lists, **kwargs)
+
+    return xor_with(
+        uniq(
+            difference_with(
+                array + lists[0],
+                intersection_with(array, lists[0], comparator=comp),
+                comparator=comp,
+            )
+        ),
+        *lists[1:],
+    )
+
+
+@t.overload
+def zip_(array1: t.Iterable[T], array2: t.Iterable[T2], /) -> t.List[t.Tuple[T, T2]]: ...
+
+
+@t.overload
+def zip_(
+    array1: t.Iterable[T], array2: t.Iterable[T2], array3: t.Iterable[T3], /
+) -> t.List[t.Tuple[T, T2, T3]]: ...
+
+
+@t.overload
+def zip_(
+    array1: t.Iterable[T], array2: t.Iterable[T2], array3: t.Iterable[T3], array4: t.Iterable[T4], /
+) -> t.List[t.Tuple[T, T2, T3, T4]]: ...
+
+
+@t.overload
+def zip_(
+    array1: t.Iterable[T],
+    array2: t.Iterable[T2],
+    array3: t.Iterable[T3],
+    array4: t.Iterable[T4],
+    array5: t.Iterable[T5],
+    /,
+) -> t.List[t.Tuple[T, T2, T3, T4, T5]]: ...
+
+
+@t.overload
+def zip_(*arrays: t.Iterable[t.Any]) -> t.List[t.Tuple[t.Any, ...]]: ...
+
+
+def zip_(*arrays):
+    """
+    Groups the elements of each array at their corresponding indexes. Useful for separate data
+    sources that are coordinated through matching array indexes.
+
+    Args:
+        arrays: Lists to process.
+
+    Returns:
+        Zipped list.
+
+    Example:
+
+        >>> zip_([1, 2, 3], [4, 5, 6], [7, 8, 9])
+        [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 8.0.0
+        Return list of tuples instead of list of lists.
+    """
+    return list(zip(*arrays))
+
+
+@t.overload
+def zip_object(keys: t.Iterable[t.Tuple[T, T2]], values: None = None) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def zip_object(
+    keys: t.Iterable[t.List[t.Union[T, T2]]], values: None = None
+) -> t.Dict[t.Union[T, T2], t.Union[T, T2]]: ...
+
+
+@t.overload
+def zip_object(keys: t.Iterable[T], values: t.List[T2]) -> t.Dict[T, T2]: ...
+
+
+def zip_object(keys, values=None):
+    """
+    Creates a dict composed of lists of keys and values. Pass either a single two-dimensional list,
+    i.e. ``[[key1, value1], [key2, value2]]``, or two lists, one of keys and one of corresponding
+    values.
+
+    Args:
+        keys: Either a list of keys or a list of ``[key, value]`` pairs.
+        values: List of values to zip.
+
+    Returns:
+        Zipped dict.
+
+    Example:
+
+        >>> zip_object([1, 2, 3], [4, 5, 6])
+        {1: 4, 2: 5, 3: 6}
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``object_``.
+    """
+    if values is None:
+        keys_values = unzip(keys)
+        if len(keys_values) == 0:
+            keys, values = [], []
+        else:
+            keys, values = keys_values
+
+    return dict(zip(keys, values))
+
+
+def zip_object_deep(
+    keys: t.Iterable[t.Any], values: t.Union[t.List[t.Any], None] = None
+) -> t.Dict[t.Any, t.Any]:
+    """
+    This method is like :func:`zip_object` except that it supports property paths.
+
+    Args:
+        keys: Either a list of keys or a list of ``[key, value]`` pairs.
+        values: List of values to zip.
+
+    Returns:
+        Zipped dict.
+
+    Example:
+
+        >>> expected = {"a": {"b": {"c": 1, "d": 2}}}
+        >>> zip_object_deep(["a.b.c", "a.b.d"], [1, 2]) == expected
+        True
+
+    .. versionadded:: 4.0.0
+    """
+    if values is None:
+        keys_values = unzip(keys)
+        if len(keys_values) == 0:
+            keys, values = [], []
+        else:
+            keys, values = keys_values
+
+    obj: t.Dict[t.Any, t.Any] = {}
+    for idx, key in enumerate(keys):
+        obj = pyd.set_(obj, key, pyd.get(values, idx))
+
+    return obj
+
+
+@t.overload
+def zip_with(
+    array1: t.Iterable[T],
+    array2: t.Iterable[T2],
+    *,
+    iteratee: t.Union[
+        t.Callable[[T, T2, int], T3],
+        t.Callable[[T, T2], T3],
+        t.Callable[[T], T3],
+    ],
+) -> t.List[T3]: ...
+
+
+@t.overload
+def zip_with(
+    *arrays: t.Iterable[t.Any],
+    iteratee: t.Union[
+        t.Callable[[t.Any, t.Any, int], T2],
+        t.Callable[[t.Any, t.Any], T2],
+        t.Callable[[t.Any], T2],
+    ],
+) -> t.List[T2]: ...
+
+
+@t.overload
+def zip_with(
+    *arrays: t.Union[
+        t.Iterable[t.Any],
+        t.Callable[[t.Any, t.Any, int], T2],
+        t.Callable[[t.Any, t.Any], T2],
+        t.Callable[[t.Any], T2],
+    ],
+) -> t.List[T2]: ...
+
+
+def zip_with(*arrays, **kwargs):
+    """
+    This method is like :func:`zip` except that it accepts an iteratee to specify how grouped values
+    should be combined. The iteratee is invoked with three arguments:
+    ``(accumulator, value, index)``.
+
+    Args:
+        *arrays: Lists to process.
+
+    Keyword Args:
+        iteratee (callable): Function to combine grouped values.
+
+    Returns:
+        Zipped list of grouped elements.
+
+    Example:
+
+        >>> from pydash import add
+        >>> zip_with([1, 2], [10, 20], [100, 200], add)
+        [111, 222]
+        >>> zip_with([1, 2], [10, 20], [100, 200], iteratee=add)
+        [111, 222]
+
+    .. versionadded:: 3.3.0
+    """
+    if "iteratee" in kwargs:
+        iteratee = kwargs["iteratee"]
+    elif len(arrays) > 1:
+        iteratee = arrays[-1]
+        arrays = arrays[:-1]
+    else:
+        iteratee = None
+
+    return unzip_with(arrays, iteratee)
+
+
+#
+# Utility methods not a part of the main API
+#
+
+
+def iterflatten(array, depth=-1):
+    """Iteratively flatten a list shallowly or deeply."""
+    for item in array:
+        if isinstance(item, (list, tuple)) and depth != 0:
+            for subitem in iterflatten(item, depth - 1):
+                yield subitem
+        else:
+            yield item
+
+
+def iterinterleave(*arrays):
+    """Interleave multiple lists."""
+    iters = [iter(arr) for arr in arrays]
+
+    while iters:
+        nextiters = []
+        for itr in iters:
+            try:
+                yield next(itr)
+                nextiters.append(itr)
+            except StopIteration:
+                pass
+
+        iters = nextiters
+
+
+def iterintersperse(iterable, separator):
+    """Iteratively intersperse iterable."""
+    iterable = iter(iterable)
+    yield next(iterable)
+    for item in iterable:
+        yield separator
+        yield item
+
+
+def iterunique(array, comparator=None, iteratee=None):  # noqa: PLR0912
+    """Yield each unique item in array."""
+    if not array:  # pragma: no cover
+        return
+
+    if iteratee is not None:
+        iteratee = pyd.iteratee(iteratee)
+
+    seen_hashable = set()
+    seen_unhashable = []
+
+    for item in array:
+        if iteratee is None:
+            cmp_item = item
+        else:
+            cmp_item = iteratee(item)
+
+        if comparator is None:
+            try:
+                if cmp_item not in seen_hashable:
+                    yield item
+                    seen_hashable.add(cmp_item)
+            except TypeError:
+                if cmp_item not in seen_unhashable:
+                    yield item
+                    seen_unhashable.append(cmp_item)
+        else:
+            unseen = True
+            for seen_item in seen_unhashable:
+                if comparator(cmp_item, seen_item):
+                    unseen = False
+                    break
+            if unseen:
+                yield item
+                seen_unhashable.append(cmp_item)
+
+
+def iterduplicates(array):
+    """Yield duplictes found in `array`."""
+    seen = []
+    for i, item in enumerate(array):
+        if item in seen:
+            yield i, item
+        else:
+            seen.append(item)
+
+
+def iterintersection(array, other, comparator=None, iteratee=None):
+    """Yield intersecting values between `array` and `other` using `comparator` to determine if they
+    intersect."""
+    if not array or not other:  # pragma: no cover
+        return
+
+    if comparator is None:
+        comparator = pyd.is_equal
+
+    iteratee = pyd.iteratee(iteratee)
+
+    # NOTE: Maintain ordering of yielded values based on `array` ordering.
+    seen = []
+    for item in array:
+        cmp_item = iteratee(item)
+
+        if cmp_item in seen:
+            continue
+
+        seen.append(cmp_item)
+        seen_others = []
+
+        for value in other:
+            cmp_value = iteratee(value)
+
+            if cmp_value in seen_others:
+                continue
+
+            seen_others.append(cmp_value)
+
+            if comparator(cmp_item, cmp_value):
+                yield item
+                break
+
+
+def iterdifference(array, other, comparator=None, iteratee=None):
+    """Yield different values in `array` as compared to `other` using `comparator` to determine if
+    they are different."""
+    if not array or not other:  # pragma: no cover
+        return
+
+    if comparator is None:
+        comparator = pyd.is_equal
+
+    iteratee = pyd.iteratee(iteratee)
+
+    def is_different(item, seen):
+        is_diff = True
+
+        if item not in seen:
+            for value in other:
+                if comparator(iteratee(item), iteratee(value)):
+                    is_diff = False
+                    break
+
+            if is_diff:
+                seen.append(item)
+        return is_diff
+
+    seen = []
+    not_seen = []
+
+    for item in array:
+        if item in not_seen or is_different(item, seen):
+            yield item
diff --git a/.venv/lib/python3.12/site-packages/pydash/chaining/__init__.py b/.venv/lib/python3.12/site-packages/pydash/chaining/__init__.py
new file mode 100644
index 00000000..0c37c37a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/chaining/__init__.py
@@ -0,0 +1,8 @@
+from .chaining import _Dash, chain, tap
+
+
+__all__ = (
+    "_Dash",
+    "chain",
+    "tap",
+)
diff --git a/.venv/lib/python3.12/site-packages/pydash/chaining/all_funcs.py b/.venv/lib/python3.12/site-packages/pydash/chaining/all_funcs.py
new file mode 100644
index 00000000..bfe63c4d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/chaining/all_funcs.py
@@ -0,0 +1,44 @@
+from abc import ABC, abstractmethod
+import typing as t
+
+
+class AllFuncs(ABC):
+    """Exposing all of the exposed functions of a module through an class."""
+
+    module: t.Any
+    invalid_method_exception: t.Type[Exception]
+
+    @abstractmethod
+    def _wrap(self, func) -> t.Callable:
+        """Proxy attribute access to :attr:`module`."""
+        raise NotImplementedError()  # pragma: no cover
+
+    @classmethod
+    def get_method(cls, name: str) -> t.Callable:
+        """
+        Return valid :attr:`module` method.
+
+        Args:
+            name: Name of pydash method to get.
+
+        Returns:
+            :attr:`module` callable.
+
+        Raises:
+            InvalidMethod: Raised if `name` is not a valid :attr:`module` method.
+        """
+        method = getattr(cls.module, name, None)
+
+        if not callable(method) and not name.endswith("_"):
+            # Alias method names not ending in underscore to their underscore
+            # counterpart. This allows chaining of functions like "map_()"
+            # using "map()" instead.
+            method = getattr(cls.module, name + "_", None)
+
+        if not callable(method):
+            raise cls.invalid_method_exception(f"Invalid {cls.module.__name__} method: {name}")
+
+        return method
+
+    def __getattr__(self, name: str) -> t.Callable:
+        return self._wrap(self.get_method(name))
diff --git a/.venv/lib/python3.12/site-packages/pydash/chaining/all_funcs.pyi b/.venv/lib/python3.12/site-packages/pydash/chaining/all_funcs.pyi
new file mode 100644
index 00000000..7c0efa7f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/chaining/all_funcs.pyi
@@ -0,0 +1,3540 @@
+# mypy: disable-error-code=misc
+"""Generated from the `scripts/chaining_type_generator.py` script."""
+
+import re
+import typing as t
+
+from _typeshed import (
+    SupportsAdd,
+    SupportsDunderGE,
+    SupportsDunderGT,
+    SupportsDunderLE,
+    SupportsDunderLT,
+    SupportsRichComparison,
+    SupportsRichComparisonT,
+    SupportsSub,
+)
+from typing_extensions import Concatenate, Literal, ParamSpec, Type
+
+import pydash as pyd
+from pydash.chaining.chaining import Chain
+from pydash.functions import (
+    After,
+    Ary,
+    Before,
+    CurryFive,
+    CurryFour,
+    CurryOne,
+    CurryRightFive,
+    CurryRightFour,
+    CurryRightOne,
+    CurryRightThree,
+    CurryRightTwo,
+    CurryThree,
+    CurryTwo,
+    Debounce,
+    Disjoin,
+    Flow,
+    Iterated,
+    Juxtapose,
+    Negate,
+    Once,
+    Partial,
+    Rearg,
+    Spread,
+    Throttle,
+)
+from pydash.helpers import UNSET, Unset
+from pydash.types import *
+from pydash.utilities import MemoizedFunc
+
+ValueT_co = t.TypeVar("ValueT_co", covariant=True)
+T = t.TypeVar("T")
+T1 = t.TypeVar("T1")
+T2 = t.TypeVar("T2")
+T3 = t.TypeVar("T3")
+T4 = t.TypeVar("T4")
+T5 = t.TypeVar("T5")
+NumT = t.TypeVar("NumT", int, float, "Decimal")
+NumT2 = t.TypeVar("NumT2", int, float, "Decimal")
+NumT3 = t.TypeVar("NumT3", int, float, "Decimal")
+CallableT = t.TypeVar("CallableT", bound=t.Callable[..., t.Any])
+SequenceT = t.TypeVar("SequenceT", bound=t.Sequence[t.Any])
+MutableSequenceT = t.TypeVar("MutableSequenceT", bound=t.MutableSequence[t.Any])
+P = ParamSpec("P")
+
+class AllFuncs:
+    def chunk(self: "Chain[t.Sequence[T]]", size: int = 1) -> "Chain[t.List[t.Sequence[T]]]":
+        return self._wrap(pyd.chunk)(size)
+
+    def compact(self: "Chain[t.Iterable[t.Union[T, None]]]") -> "Chain[t.List[T]]":
+        return self._wrap(pyd.compact)()
+
+    def concat(self: "Chain[t.Iterable[T]]", *arrays: t.Iterable[T]) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.concat)(*arrays)
+
+    def difference(self: "Chain[t.Iterable[T]]", *others: t.Iterable[T]) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.difference)(*others)
+
+    @t.overload
+    def difference_by(
+        self: "Chain[t.Iterable[T]]",
+        *others: t.Iterable[T],
+        iteratee: t.Union[IterateeObjT, t.Callable[[T], t.Any], None],
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def difference_by(
+        self: "Chain[t.Iterable[T]]",
+        *others: t.Union[IterateeObjT, t.Iterable[T], t.Callable[[T], t.Any]],
+    ) -> "Chain[t.List[T]]": ...
+    def difference_by(self, *others, **kwargs):
+        return self._wrap(pyd.difference_by)(*others, **kwargs)
+
+    @t.overload
+    def difference_with(
+        self: "Chain[t.Iterable[T]]",
+        *others: t.Iterable[T2],
+        comparator: t.Union[t.Callable[[T, T2], t.Any], None],
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def difference_with(
+        self: "Chain[t.Iterable[T]]", *others: t.Union[t.Iterable[T2], t.Callable[[T, T2], t.Any]]
+    ) -> "Chain[t.List[T]]": ...
+    def difference_with(self, *others, **kwargs):
+        return self._wrap(pyd.difference_with)(*others, **kwargs)
+
+    def drop(self: "Chain[t.Sequence[T]]", n: int = 1) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.drop)(n)
+
+    def drop_right(self: "Chain[t.Sequence[T]]", n: int = 1) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.drop_right)(n)
+
+    @t.overload
+    def drop_right_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def drop_right_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def drop_right_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def drop_right_while(
+        self: "Chain[t.Sequence[T]]", predicate: None = None
+    ) -> "Chain[t.List[T]]": ...
+    def drop_right_while(self, predicate=None):
+        return self._wrap(pyd.drop_right_while)(predicate)
+
+    @t.overload
+    def drop_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def drop_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def drop_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def drop_while(self: "Chain[t.Sequence[T]]", predicate: None = None) -> "Chain[t.List[T]]": ...
+    def drop_while(self, predicate=None):
+        return self._wrap(pyd.drop_while)(predicate)
+
+    def duplicates(
+        self: "Chain[t.Sequence[T]]",
+        iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.duplicates)(iteratee)
+
+    def fill(
+        self: "Chain[t.Sequence[T]]", value: T2, start: int = 0, end: t.Union[int, None] = None
+    ) -> "Chain[t.List[t.Union[T, T2]]]":
+        return self._wrap(pyd.fill)(value, start, end)
+
+    @t.overload
+    def find_index(
+        self: "Chain[t.Iterable[T]]", predicate: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[int]": ...
+    @t.overload
+    def find_index(
+        self: "Chain[t.Iterable[T]]", predicate: t.Callable[[T, int], t.Any]
+    ) -> "Chain[int]": ...
+    @t.overload
+    def find_index(
+        self: "Chain[t.Iterable[T]]", predicate: t.Callable[[T], t.Any]
+    ) -> "Chain[int]": ...
+    @t.overload
+    def find_index(self: "Chain[t.Iterable[t.Any]]", predicate: IterateeObjT) -> "Chain[int]": ...
+    @t.overload
+    def find_index(self: "Chain[t.Iterable[t.Any]]", predicate: None = None) -> "Chain[int]": ...
+    def find_index(self, predicate=None):
+        return self._wrap(pyd.find_index)(predicate)
+
+    @t.overload
+    def find_last_index(
+        self: "Chain[t.Iterable[T]]", predicate: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[int]": ...
+    @t.overload
+    def find_last_index(
+        self: "Chain[t.Iterable[T]]", predicate: t.Callable[[T, int], t.Any]
+    ) -> "Chain[int]": ...
+    @t.overload
+    def find_last_index(
+        self: "Chain[t.Iterable[T]]", predicate: t.Callable[[T], t.Any]
+    ) -> "Chain[int]": ...
+    @t.overload
+    def find_last_index(
+        self: "Chain[t.Iterable[t.Any]]", predicate: IterateeObjT
+    ) -> "Chain[int]": ...
+    @t.overload
+    def find_last_index(
+        self: "Chain[t.Iterable[t.Any]]", predicate: None = None
+    ) -> "Chain[int]": ...
+    def find_last_index(self, predicate=None):
+        return self._wrap(pyd.find_last_index)(predicate)
+
+    @t.overload
+    def flatten(self: "Chain[t.Iterable[t.Iterable[T]]]") -> "Chain[t.List[T]]": ...
+    @t.overload
+    def flatten(self: "Chain[t.Iterable[T]]") -> "Chain[t.List[T]]": ...
+    def flatten(self):
+        return self._wrap(pyd.flatten)()
+
+    def flatten_deep(self: "Chain[t.Iterable[t.Any]]") -> "Chain[t.List[t.Any]]":
+        return self._wrap(pyd.flatten_deep)()
+
+    def flatten_depth(self: "Chain[t.Iterable[t.Any]]", depth: int = 1) -> "Chain[t.List[t.Any]]":
+        return self._wrap(pyd.flatten_depth)(depth)
+
+    @t.overload
+    def from_pairs(self: "Chain[t.Iterable[t.Tuple[T, T2]]]") -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def from_pairs(
+        self: "Chain[t.Iterable[t.List[t.Union[T, T2]]]]",
+    ) -> "Chain[t.Dict[t.Union[T, T2], t.Union[T, T2]]]": ...
+    def from_pairs(self):
+        return self._wrap(pyd.from_pairs)()
+
+    def head(self: "Chain[t.Sequence[T]]") -> "Chain[t.Union[T, None]]":
+        return self._wrap(pyd.head)()
+
+    def index_of(self: "Chain[t.Sequence[T]]", value: T, from_index: int = 0) -> "Chain[int]":
+        return self._wrap(pyd.index_of)(value, from_index)
+
+    def initial(self: "Chain[t.Sequence[T]]") -> "Chain[t.Sequence[T]]":
+        return self._wrap(pyd.initial)()
+
+    @t.overload
+    def intercalate(
+        self: "Chain[t.Iterable[t.Iterable[T]]]", separator: T2
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    @t.overload
+    def intercalate(
+        self: "Chain[t.Iterable[T]]", separator: T2
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    def intercalate(self, separator):
+        return self._wrap(pyd.intercalate)(separator)
+
+    def interleave(self: "Chain[t.Iterable[T]]", *arrays: t.Iterable[T]) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.interleave)(*arrays)
+
+    def intersection(
+        self: "Chain[t.Sequence[T]]", *others: t.Iterable[t.Any]
+    ) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.intersection)(*others)
+
+    @t.overload
+    def intersection_by(
+        self: "Chain[t.Sequence[T]]",
+        *others: t.Iterable[t.Any],
+        iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT],
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def intersection_by(
+        self: "Chain[t.Sequence[T]]",
+        *others: t.Union[t.Iterable[t.Any], t.Callable[[T], t.Any], IterateeObjT],
+    ) -> "Chain[t.List[T]]": ...
+    def intersection_by(self, *others, **kwargs):
+        return self._wrap(pyd.intersection_by)(*others, **kwargs)
+
+    @t.overload
+    def intersection_with(
+        self: "Chain[t.Sequence[T]]",
+        *others: t.Iterable[T2],
+        comparator: t.Callable[[T, T2], t.Any],
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def intersection_with(
+        self: "Chain[t.Sequence[T]]", *others: t.Union[t.Iterable[T2], t.Callable[[T, T2], t.Any]]
+    ) -> "Chain[t.List[T]]": ...
+    def intersection_with(self, *others, **kwargs):
+        return self._wrap(pyd.intersection_with)(*others, **kwargs)
+
+    def intersperse(self: "Chain[t.Iterable[T]]", separator: T2) -> "Chain[t.List[t.Union[T, T2]]]":
+        return self._wrap(pyd.intersperse)(separator)
+
+    def last(self: "Chain[t.Sequence[T]]") -> "Chain[t.Union[T, None]]":
+        return self._wrap(pyd.last)()
+
+    def last_index_of(
+        self: "Chain[t.Sequence[t.Any]]", value: t.Any, from_index: t.Union[int, None] = None
+    ) -> "Chain[int]":
+        return self._wrap(pyd.last_index_of)(value, from_index)
+
+    @t.overload
+    def mapcat(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Callable[[T, int, t.List[T]], t.Union[t.List[T2], t.List[t.List[T2]]]],
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def mapcat(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], T2]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def mapcat(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Callable[[T, int], t.Union[t.List[T2], t.List[t.List[T2]]]],
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def mapcat(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], T2]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def mapcat(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Callable[[T], t.Union[t.List[T2], t.List[t.List[T2]]]],
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def mapcat(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], T2]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def mapcat(
+        self: "Chain[t.Iterable[t.Union[t.List[T], t.List[t.List[T]]]]]", iteratee: None = None
+    ) -> "Chain[t.List[t.Union[T, t.List[T]]]]": ...
+    def mapcat(self, iteratee=None):
+        return self._wrap(pyd.mapcat)(iteratee)
+
+    def nth(self: "Chain[t.Iterable[T]]", pos: int = 0) -> "Chain[t.Union[T, None]]":
+        return self._wrap(pyd.nth)(pos)
+
+    def pop(self: "Chain[t.List[T]]", index: int = -1) -> "Chain[T]":
+        return self._wrap(pyd.pop)(index)
+
+    def pull(self: "Chain[t.List[T]]", *values: T) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.pull)(*values)
+
+    def pull_all(self: "Chain[t.List[T]]", values: t.Iterable[T]) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.pull_all)(values)
+
+    def pull_all_by(
+        self: "Chain[t.List[T]]",
+        values: t.Iterable[T],
+        iteratee: t.Union[IterateeObjT, t.Callable[[T], t.Any], None] = None,
+    ) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.pull_all_by)(values, iteratee)
+
+    def pull_all_with(
+        self: "Chain[t.List[T]]",
+        values: t.Iterable[T],
+        comparator: t.Union[t.Callable[[T, T], t.Any], None] = None,
+    ) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.pull_all_with)(values, comparator)
+
+    def pull_at(self: "Chain[t.List[T]]", *indexes: int) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.pull_at)(*indexes)
+
+    def push(self: "Chain[t.List[T]]", *items: T2) -> "Chain[t.List[t.Union[T, T2]]]":
+        return self._wrap(pyd.push)(*items)
+
+    def remove(
+        self: "Chain[t.List[T]]",
+        predicate: t.Union[
+            t.Callable[[T, int, t.List[T]], t.Any],
+            t.Callable[[T, int], t.Any],
+            t.Callable[[T], t.Any],
+            None,
+        ] = None,
+    ) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.remove)(predicate)
+
+    def reverse(self: "Chain[SequenceT]") -> "Chain[SequenceT]":
+        return self._wrap(pyd.reverse)()
+
+    def shift(self: "Chain[t.List[T]]") -> "Chain[T]":
+        return self._wrap(pyd.shift)()
+
+    def slice_(
+        self: "Chain[SequenceT]", start: int = 0, end: t.Union[int, None] = None
+    ) -> "Chain[SequenceT]":
+        return self._wrap(pyd.slice_)(start, end)
+
+    slice = slice_
+
+    @t.overload
+    def sort(
+        self: "Chain[t.List['SupportsRichComparisonT']]",
+        comparator: None = None,
+        key: None = None,
+        reverse: bool = False,
+    ) -> "Chain[t.List['SupportsRichComparisonT']]": ...
+    @t.overload
+    def sort(
+        self: "Chain[t.List[T]]", comparator: t.Callable[[T, T], int], *, reverse: bool = False
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def sort(
+        self: "Chain[t.List[T]]",
+        *,
+        key: t.Callable[[T], "SupportsRichComparisonT"],
+        reverse: bool = False,
+    ) -> "Chain[t.List[T]]": ...
+    def sort(self, comparator=None, key=None, reverse=False):
+        return self._wrap(pyd.sort)(comparator, key, reverse)
+
+    def sorted_index(
+        self: "Chain[t.Sequence['SupportsRichComparisonT']]", value: "SupportsRichComparisonT"
+    ) -> "Chain[int]":
+        return self._wrap(pyd.sorted_index)(value)
+
+    @t.overload
+    def sorted_index_by(
+        self: "Chain[t.Sequence[T]]",
+        value: T,
+        iteratee: t.Union[IterateeObjT, t.Callable[[T], "SupportsRichComparisonT"]],
+    ) -> "Chain[int]": ...
+    @t.overload
+    def sorted_index_by(
+        self: "Chain[t.Sequence['SupportsRichComparisonT']]",
+        value: "SupportsRichComparisonT",
+        iteratee: None = None,
+    ) -> "Chain[int]": ...
+    def sorted_index_by(self, value, iteratee=None):
+        return self._wrap(pyd.sorted_index_by)(value, iteratee)
+
+    def sorted_index_of(
+        self: "Chain[t.Sequence['SupportsRichComparisonT']]", value: "SupportsRichComparisonT"
+    ) -> "Chain[int]":
+        return self._wrap(pyd.sorted_index_of)(value)
+
+    def sorted_last_index(
+        self: "Chain[t.Sequence['SupportsRichComparisonT']]", value: "SupportsRichComparisonT"
+    ) -> "Chain[int]":
+        return self._wrap(pyd.sorted_last_index)(value)
+
+    @t.overload
+    def sorted_last_index_by(
+        self: "Chain[t.Sequence[T]]",
+        value: T,
+        iteratee: t.Union[IterateeObjT, t.Callable[[T], "SupportsRichComparisonT"]],
+    ) -> "Chain[int]": ...
+    @t.overload
+    def sorted_last_index_by(
+        self: "Chain[t.Sequence['SupportsRichComparisonT']]",
+        value: "SupportsRichComparisonT",
+        iteratee: None = None,
+    ) -> "Chain[int]": ...
+    def sorted_last_index_by(self, value, iteratee=None):
+        return self._wrap(pyd.sorted_last_index_by)(value, iteratee)
+
+    def sorted_last_index_of(
+        self: "Chain[t.Sequence['SupportsRichComparisonT']]", value: "SupportsRichComparisonT"
+    ) -> "Chain[int]":
+        return self._wrap(pyd.sorted_last_index_of)(value)
+
+    def sorted_uniq(
+        self: "Chain[t.Iterable['SupportsRichComparisonT']]",
+    ) -> "Chain[t.List['SupportsRichComparisonT']]":
+        return self._wrap(pyd.sorted_uniq)()
+
+    def sorted_uniq_by(
+        self: "Chain[t.Iterable['SupportsRichComparisonT']]",
+        iteratee: t.Union[
+            t.Callable[["SupportsRichComparisonT"], "SupportsRichComparisonT"], None
+        ] = None,
+    ) -> "Chain[t.List['SupportsRichComparisonT']]":
+        return self._wrap(pyd.sorted_uniq_by)(iteratee)
+
+    def splice(
+        self: "Chain[MutableSequenceT]", start: int, count: t.Union[int, None] = None, *items: t.Any
+    ) -> "Chain[MutableSequenceT]":
+        return self._wrap(pyd.splice)(start, count, *items)
+
+    def split_at(self: "Chain[t.Sequence[T]]", index: int) -> "Chain[t.List[t.Sequence[T]]]":
+        return self._wrap(pyd.split_at)(index)
+
+    def tail(self: "Chain[t.Sequence[T]]") -> "Chain[t.Sequence[T]]":
+        return self._wrap(pyd.tail)()
+
+    def take(self: "Chain[t.Sequence[T]]", n: int = 1) -> "Chain[t.Sequence[T]]":
+        return self._wrap(pyd.take)(n)
+
+    def take_right(self: "Chain[t.Sequence[T]]", n: int = 1) -> "Chain[t.Sequence[T]]":
+        return self._wrap(pyd.take_right)(n)
+
+    @t.overload
+    def take_right_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[t.Sequence[T]]": ...
+    @t.overload
+    def take_right_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.Sequence[T]]": ...
+    @t.overload
+    def take_right_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T], t.Any]
+    ) -> "Chain[t.Sequence[T]]": ...
+    @t.overload
+    def take_right_while(
+        self: "Chain[t.Sequence[T]]", predicate: None = None
+    ) -> "Chain[t.Sequence[T]]": ...
+    def take_right_while(self, predicate=None):
+        return self._wrap(pyd.take_right_while)(predicate)
+
+    @t.overload
+    def take_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def take_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def take_while(
+        self: "Chain[t.Sequence[T]]", predicate: t.Callable[[T], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def take_while(self: "Chain[t.Sequence[T]]", predicate: None = None) -> "Chain[t.List[T]]": ...
+    def take_while(self, predicate=None):
+        return self._wrap(pyd.take_while)(predicate)
+
+    @t.overload
+    def union(self: "Chain[t.Sequence[T]]") -> "Chain[t.List[T]]": ...
+    @t.overload
+    def union(
+        self: "Chain[t.Sequence[T]]", *others: t.Sequence[T2]
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    def union(self, *others):
+        return self._wrap(pyd.union)(*others)
+
+    @t.overload
+    def union_by(
+        self: "Chain[t.Sequence[T]]", *others: t.Iterable[T], iteratee: t.Callable[[T], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def union_by(
+        self: "Chain[t.Sequence[T]]", *others: t.Union[t.Iterable[T], t.Callable[[T], t.Any]]
+    ) -> "Chain[t.List[T]]": ...
+    def union_by(self, *others, **kwargs):
+        return self._wrap(pyd.union_by)(*others, **kwargs)
+
+    @t.overload
+    def union_with(
+        self: "Chain[t.Sequence[T]]",
+        *others: t.Iterable[T2],
+        comparator: t.Callable[[T, T2], t.Any],
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def union_with(
+        self: "Chain[t.Sequence[T]]", *others: t.Union[t.Iterable[T2], t.Callable[[T, T2], t.Any]]
+    ) -> "Chain[t.List[T]]": ...
+    def union_with(self, *others, **kwargs):
+        return self._wrap(pyd.union_with)(*others, **kwargs)
+
+    def uniq(self: "Chain[t.Iterable[T]]") -> "Chain[t.List[T]]":
+        return self._wrap(pyd.uniq)()
+
+    def uniq_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Union[t.Callable[[T], t.Any], None] = None
+    ) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.uniq_by)(iteratee)
+
+    def uniq_with(
+        self: "Chain[t.Sequence[T]]", comparator: t.Union[t.Callable[[T, T], t.Any], None] = None
+    ) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.uniq_with)(comparator)
+
+    def unshift(self: "Chain[t.List[T]]", *items: T2) -> "Chain[t.List[t.Union[T, T2]]]":
+        return self._wrap(pyd.unshift)(*items)
+
+    @t.overload
+    def unzip(self: "Chain[t.Iterable[t.Tuple[T, T2]]]") -> "Chain[t.List[t.Tuple[T, T2]]]": ...
+    @t.overload
+    def unzip(
+        self: "Chain[t.Iterable[t.Tuple[T, T2, T3]]]",
+    ) -> "Chain[t.List[t.Tuple[T, T2, T3]]]": ...
+    @t.overload
+    def unzip(
+        self: "Chain[t.Iterable[t.Tuple[T, T2, T3, T4]]]",
+    ) -> "Chain[t.List[t.Tuple[T, T2, T3, T4]]]": ...
+    @t.overload
+    def unzip(
+        self: "Chain[t.Iterable[t.Tuple[T, T2, T3, T4, T5]]]",
+    ) -> "Chain[t.List[t.Tuple[T, T2, T3, T4, T5]]]": ...
+    @t.overload
+    def unzip(
+        self: "Chain[t.Iterable[t.Iterable[t.Any]]]",
+    ) -> "Chain[t.List[t.Tuple[t.Any, ...]]]": ...
+    def unzip(self):
+        return self._wrap(pyd.unzip)()
+
+    @t.overload
+    def unzip_with(
+        self: "Chain[t.Iterable[t.Tuple[T, T2]]]",
+        iteratee: t.Union[
+            t.Callable[[t.Union[T, T2, T3], t.Union[T, T2], int], T3],
+            t.Callable[[t.Union[T, T2, T3], t.Union[T, T2]], T3],
+            t.Callable[[t.Union[T, T2, T3]], T3],
+        ],
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def unzip_with(
+        self: "Chain[t.Iterable[t.Iterable[t.Any]]]",
+        iteratee: t.Union[
+            t.Callable[[t.Any, t.Any, int], T3],
+            t.Callable[[t.Any, t.Any], T3],
+            t.Callable[[t.Any], T3],
+        ],
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def unzip_with(
+        self: "Chain[t.Iterable[t.Iterable[T]]]", iteratee: None = None
+    ) -> "Chain[t.List[t.Tuple[T]]]": ...
+    def unzip_with(self, iteratee=None):
+        return self._wrap(pyd.unzip_with)(iteratee)
+
+    def without(self: "Chain[t.Iterable[T]]", *values: T) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.without)(*values)
+
+    def xor(self: "Chain[t.Iterable[T]]", *lists: t.Iterable[T]) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.xor)(*lists)
+
+    @t.overload
+    def xor_by(
+        self: "Chain[t.Iterable[T]]",
+        *lists: t.Iterable[T],
+        iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT],
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def xor_by(
+        self: "Chain[t.Iterable[T]]", *lists: t.Union[t.Iterable[T], t.Callable[[T], t.Any]]
+    ) -> "Chain[t.List[T]]": ...
+    def xor_by(self, *lists, **kwargs):
+        return self._wrap(pyd.xor_by)(*lists, **kwargs)
+
+    @t.overload
+    def xor_with(
+        self: "Chain[t.Sequence[T]]", *lists: t.Iterable[T2], comparator: t.Callable[[T, T2], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def xor_with(
+        self: "Chain[t.Sequence[T]]", *lists: t.Union[t.Iterable[T2], t.Callable[[T, T2], t.Any]]
+    ) -> "Chain[t.List[T]]": ...
+    def xor_with(self, *lists, **kwargs):
+        return self._wrap(pyd.xor_with)(*lists, **kwargs)
+
+    @t.overload
+    def zip_(
+        self: "Chain[t.Iterable[t.Any]]", *arrays: t.Iterable[t.Any]
+    ) -> "Chain[t.List[t.Tuple[t.Any, ...]]]": ...
+    def zip_(self, *arrays):
+        return self._wrap(pyd.zip_)(*arrays)
+
+    zip = zip_
+
+    @t.overload
+    def zip_object(
+        self: "Chain[t.Iterable[t.Tuple[T, T2]]]", values: None = None
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def zip_object(
+        self: "Chain[t.Iterable[t.List[t.Union[T, T2]]]]", values: None = None
+    ) -> "Chain[t.Dict[t.Union[T, T2], t.Union[T, T2]]]": ...
+    @t.overload
+    def zip_object(self: "Chain[t.Iterable[T]]", values: t.List[T2]) -> "Chain[t.Dict[T, T2]]": ...
+    def zip_object(self, values=None):
+        return self._wrap(pyd.zip_object)(values)
+
+    def zip_object_deep(
+        self: "Chain[t.Iterable[t.Any]]", values: t.Union[t.List[t.Any], None] = None
+    ) -> "Chain[t.Dict[t.Any, t.Any]]":
+        return self._wrap(pyd.zip_object_deep)(values)
+
+    @t.overload
+    def zip_with(
+        self: "Chain[t.Iterable[T]]",
+        array2: t.Iterable[T2],
+        *,
+        iteratee: t.Union[
+            t.Callable[[T, T2, int], T3], t.Callable[[T, T2], T3], t.Callable[[T], T3]
+        ],
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def zip_with(
+        self: "Chain[t.Iterable[t.Any]]",
+        *arrays: t.Iterable[t.Any],
+        iteratee: t.Union[
+            t.Callable[[t.Any, t.Any, int], T2],
+            t.Callable[[t.Any, t.Any], T2],
+            t.Callable[[t.Any], T2],
+        ],
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def zip_with(
+        self: "Chain[t.Union[t.Iterable[t.Any], t.Callable[[t.Any, t.Any, int], T2], t.Callable[[t.Any, t.Any], T2], t.Callable[[t.Any], T2]]]",
+        *arrays: t.Union[
+            t.Iterable[t.Any],
+            t.Callable[[t.Any, t.Any, int], T2],
+            t.Callable[[t.Any, t.Any], T2],
+            t.Callable[[t.Any], T2],
+        ],
+    ) -> "Chain[t.List[T2]]": ...
+    def zip_with(self, *arrays, **kwargs):
+        return self._wrap(pyd.zip_with)(*arrays, **kwargs)
+
+    def tap(self: "Chain[T]", interceptor: t.Callable[[T], t.Any]) -> "Chain[T]":
+        return self._wrap(pyd.tap)(interceptor)
+
+    @t.overload
+    def at(self: "Chain[t.Mapping[T, T2]]", *paths: T) -> "Chain[t.List[t.Union[T2, None]]]": ...
+    @t.overload
+    def at(
+        self: "Chain[t.Mapping[T, t.Any]]", *paths: t.Union[T, t.Iterable[T]]
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def at(self: "Chain[t.Iterable[T]]", *paths: int) -> "Chain[t.List[t.Union[T, None]]]": ...
+    @t.overload
+    def at(
+        self: "Chain[t.Iterable[t.Any]]", *paths: t.Union[int, t.Iterable[int]]
+    ) -> "Chain[t.List[t.Any]]": ...
+    def at(self, *paths):
+        return self._wrap(pyd.at)(*paths)
+
+    @t.overload
+    def count_by(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: None = None
+    ) -> "Chain[t.Dict[T2, int]]": ...
+    @t.overload
+    def count_by(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T, t.Dict[T, T2]], T3]
+    ) -> "Chain[t.Dict[T3, int]]": ...
+    @t.overload
+    def count_by(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], T3]
+    ) -> "Chain[t.Dict[T3, int]]": ...
+    @t.overload
+    def count_by(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T2], T3]
+    ) -> "Chain[t.Dict[T3, int]]": ...
+    @t.overload
+    def count_by(
+        self: "Chain[t.Iterable[T]]", iteratee: None = None
+    ) -> "Chain[t.Dict[T, int]]": ...
+    @t.overload
+    def count_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], T2]
+    ) -> "Chain[t.Dict[T2, int]]": ...
+    @t.overload
+    def count_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], T2]
+    ) -> "Chain[t.Dict[T2, int]]": ...
+    @t.overload
+    def count_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], T2]
+    ) -> "Chain[t.Dict[T2, int]]": ...
+    def count_by(self, iteratee=None):
+        return self._wrap(pyd.count_by)(iteratee)
+
+    def every(
+        self: "Chain[t.Iterable[T]]",
+        predicate: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.every)(predicate)
+
+    @t.overload
+    def filter_(
+        self: "Chain[t.Mapping[T, T2]]",
+        predicate: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def filter_(
+        self: "Chain[t.Mapping[T, T2]]",
+        predicate: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def filter_(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        predicate: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def filter_(
+        self: "Chain[t.Iterable[T]]",
+        predicate: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def filter_(
+        self: "Chain[t.Iterable[T]]",
+        predicate: t.Union[t.Callable[[T, int], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def filter_(
+        self: "Chain[t.Iterable[T]]",
+        predicate: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T]]": ...
+    def filter_(self, predicate=None):
+        return self._wrap(pyd.filter_)(predicate)
+
+    filter = filter_
+
+    @t.overload
+    def find(
+        self: "Chain[t.Dict[T, T2]]",
+        predicate: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T2, None]]": ...
+    @t.overload
+    def find(
+        self: "Chain[t.Dict[T, T2]]",
+        predicate: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T2, None]]": ...
+    @t.overload
+    def find(
+        self: "Chain[t.Dict[T, T2]]",
+        predicate: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T2, None]]": ...
+    @t.overload
+    def find(
+        self: "Chain[t.List[T]]",
+        predicate: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find(
+        self: "Chain[t.List[T]]",
+        predicate: t.Union[t.Callable[[T, int], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find(
+        self: "Chain[t.List[T]]",
+        predicate: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T, None]]": ...
+    def find(self, predicate=None):
+        return self._wrap(pyd.find)(predicate)
+
+    @t.overload
+    def find_last(
+        self: "Chain[t.Dict[T, T2]]",
+        predicate: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T2, None]]": ...
+    @t.overload
+    def find_last(
+        self: "Chain[t.Dict[T, T2]]",
+        predicate: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T2, None]]": ...
+    @t.overload
+    def find_last(
+        self: "Chain[t.Dict[t.Any, T2]]",
+        predicate: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T2, None]]": ...
+    @t.overload
+    def find_last(
+        self: "Chain[t.List[T]]",
+        predicate: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find_last(
+        self: "Chain[t.List[T]]",
+        predicate: t.Union[t.Callable[[T, int], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find_last(
+        self: "Chain[t.List[T]]",
+        predicate: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Union[T, None]]": ...
+    def find_last(self, predicate=None):
+        return self._wrap(pyd.find_last)(predicate)
+
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Callable[[T2, T, t.Dict[T, T2]], t.Iterable[T3]],
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], t.Iterable[T3]]
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T2], t.Iterable[T3]]
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T, t.Dict[T, T2]], T3]
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], T3]
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T2], T3]
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Mapping[t.Any, t.Iterable[T2]]]", iteratee: None = None
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: None = None
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], t.Iterable[T2]]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], t.Iterable[T2]]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], t.Iterable[T2]]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], T2]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], T2]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], T2]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def flat_map(
+        self: "Chain[t.Iterable[t.Iterable[T]]]", iteratee: None = None
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def flat_map(self: "Chain[t.Iterable[T]]", iteratee: None = None) -> "Chain[t.List[T]]": ...
+    def flat_map(self, iteratee=None):
+        return self._wrap(pyd.flat_map)(iteratee)
+
+    @t.overload
+    def flat_map_deep(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], None] = None,
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def flat_map_deep(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Union[t.Callable[[T2, T], t.Any], None] = None
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def flat_map_deep(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Union[t.Callable[[T2], t.Any], None] = None
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def flat_map_deep(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Union[t.Callable[[T, int, t.List[T]], t.Any], None] = None,
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def flat_map_deep(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Union[t.Callable[[T, int], t.Any], None] = None
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def flat_map_deep(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Union[t.Callable[[T], t.Any], None] = None
+    ) -> "Chain[t.List[t.Any]]": ...
+    def flat_map_deep(self, iteratee=None):
+        return self._wrap(pyd.flat_map_deep)(iteratee)
+
+    @t.overload
+    def flat_map_depth(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], None] = None,
+        depth: int = 1,
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def flat_map_depth(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Union[t.Callable[[T2, T], t.Any], None] = None,
+        depth: int = 1,
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def flat_map_depth(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Union[t.Callable[[T2], t.Any], None] = None,
+        depth: int = 1,
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def flat_map_depth(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Union[t.Callable[[T, int, t.List[T]], t.Any], None] = None,
+        depth: int = 1,
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def flat_map_depth(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Union[t.Callable[[T, int], t.Any], None] = None,
+        depth: int = 1,
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def flat_map_depth(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Union[t.Callable[[T], t.Any], None] = None,
+        depth: int = 1,
+    ) -> "Chain[t.List[t.Any]]": ...
+    def flat_map_depth(self, iteratee=None, depth=1):
+        return self._wrap(pyd.flat_map_depth)(iteratee, depth)
+
+    @t.overload
+    def for_each(
+        self: "Chain[t.Dict[T, T2]]",
+        iteratee: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_each(
+        self: "Chain[t.Dict[T, T2]]",
+        iteratee: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_each(
+        self: "Chain[t.Dict[T, T2]]",
+        iteratee: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_each(
+        self: "Chain[t.List[T]]",
+        iteratee: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def for_each(
+        self: "Chain[t.List[T]]",
+        iteratee: t.Union[t.Callable[[T, int], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def for_each(
+        self: "Chain[t.List[T]]",
+        iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T]]": ...
+    def for_each(self, iteratee=None):
+        return self._wrap(pyd.for_each)(iteratee)
+
+    @t.overload
+    def for_each_right(
+        self: "Chain[t.Dict[T, T2]]",
+        iteratee: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT],
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_each_right(
+        self: "Chain[t.Dict[T, T2]]", iteratee: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_each_right(
+        self: "Chain[t.Dict[T, T2]]", iteratee: t.Union[t.Callable[[T2], t.Any], IterateeObjT]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_each_right(
+        self: "Chain[t.List[T]]",
+        iteratee: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT],
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def for_each_right(
+        self: "Chain[t.List[T]]", iteratee: t.Union[t.Callable[[T, int], t.Any], IterateeObjT]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def for_each_right(
+        self: "Chain[t.List[T]]", iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT]
+    ) -> "Chain[t.List[T]]": ...
+    def for_each_right(self, iteratee):
+        return self._wrap(pyd.for_each_right)(iteratee)
+
+    @t.overload
+    def group_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], T2]
+    ) -> "Chain[t.Dict[T2, t.List[T]]]": ...
+    @t.overload
+    def group_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Union[IterateeObjT, None] = None
+    ) -> "Chain[t.Dict[t.Any, t.List[T]]]": ...
+    def group_by(self, iteratee=None):
+        return self._wrap(pyd.group_by)(iteratee)
+
+    def includes(
+        self: "Chain[t.Union[t.Sequence[t.Any], t.Dict[t.Any, t.Any]]]",
+        target: t.Any,
+        from_index: int = 0,
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.includes)(target, from_index)
+
+    def invoke_map(
+        self: "Chain[t.Iterable[t.Any]]", path: PathT, *args: t.Any, **kwargs: t.Any
+    ) -> "Chain[t.List[t.Any]]":
+        return self._wrap(pyd.invoke_map)(path, *args, **kwargs)
+
+    @t.overload
+    def key_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], T2]
+    ) -> "Chain[t.Dict[T2, T]]": ...
+    @t.overload
+    def key_by(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Union[IterateeObjT, None] = None
+    ) -> "Chain[t.Dict[t.Any, t.Any]]": ...
+    def key_by(self, iteratee=None):
+        return self._wrap(pyd.key_by)(iteratee)
+
+    @t.overload
+    def map_(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T2], T3]
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def map_(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], T3]
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def map_(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T, t.Dict[T, T2]], T3]
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def map_(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], T2]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def map_(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], T2]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def map_(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], T2]
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def map_(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Union[IterateeObjT, None] = None
+    ) -> "Chain[t.List[t.Any]]": ...
+    def map_(self, iteratee=None):
+        return self._wrap(pyd.map_)(iteratee)
+
+    map = map_
+
+    def nest(self: "Chain[t.Iterable[t.Any]]", *properties: t.Any) -> "Chain[t.Any]":
+        return self._wrap(pyd.nest)(*properties)
+
+    @t.overload
+    def order_by(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        keys: t.Iterable[t.Union[str, int]],
+        orders: t.Union[t.Iterable[bool], bool],
+        reverse: bool = False,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def order_by(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        keys: t.Iterable[str],
+        orders: None = None,
+        reverse: bool = False,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def order_by(
+        self: "Chain[t.Iterable[T]]",
+        keys: t.Iterable[t.Union[str, int]],
+        orders: t.Union[t.Iterable[bool], bool],
+        reverse: bool = False,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def order_by(
+        self: "Chain[t.Iterable[T]]",
+        keys: t.Iterable[str],
+        orders: None = None,
+        reverse: bool = False,
+    ) -> "Chain[t.List[T]]": ...
+    def order_by(self, keys, orders=None, reverse=False):
+        return self._wrap(pyd.order_by)(keys, orders, reverse)
+
+    @t.overload
+    def partition(
+        self: "Chain[t.Mapping[T, T2]]", predicate: t.Callable[[T2, T, t.Dict[T, T2]], t.Any]
+    ) -> "Chain[t.List[t.List[T2]]]": ...
+    @t.overload
+    def partition(
+        self: "Chain[t.Mapping[T, T2]]", predicate: t.Callable[[T2, T], t.Any]
+    ) -> "Chain[t.List[t.List[T2]]]": ...
+    @t.overload
+    def partition(
+        self: "Chain[t.Mapping[t.Any, T2]]", predicate: t.Callable[[T2], t.Any]
+    ) -> "Chain[t.List[t.List[T2]]]": ...
+    @t.overload
+    def partition(
+        self: "Chain[t.Mapping[t.Any, T2]]", predicate: t.Union[IterateeObjT, None] = None
+    ) -> "Chain[t.List[t.List[T2]]]": ...
+    @t.overload
+    def partition(
+        self: "Chain[t.Iterable[T]]", predicate: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[t.List[t.List[T]]]": ...
+    @t.overload
+    def partition(
+        self: "Chain[t.Iterable[T]]", predicate: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.List[t.List[T]]]": ...
+    @t.overload
+    def partition(
+        self: "Chain[t.Iterable[T]]", predicate: t.Callable[[T], t.Any]
+    ) -> "Chain[t.List[t.List[T]]]": ...
+    @t.overload
+    def partition(
+        self: "Chain[t.Iterable[T]]", predicate: t.Union[IterateeObjT, None] = None
+    ) -> "Chain[t.List[t.List[T]]]": ...
+    def partition(self, predicate=None):
+        return self._wrap(pyd.partition)(predicate)
+
+    def pluck(self: "Chain[t.Iterable[t.Any]]", path: PathT) -> "Chain[t.List[t.Any]]":
+        return self._wrap(pyd.pluck)(path)
+
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T3, T2, T], T3], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T3, T2], T3], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Mapping[t.Any, t.Any]]", iteratee: t.Callable[[T3], T3], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Callable[[T2, T2, T], T2],
+        accumulator: None = None,
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Callable[[T2, T2], T2],
+        accumulator: None = None,
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Mapping[t.Any, t.Any]]",
+        iteratee: t.Callable[[T], T],
+        accumulator: None = None,
+    ) -> "Chain[T]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T2, T, int], T2], accumulator: T2
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T2, T], T2], accumulator: T2
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Callable[[T2], T2], accumulator: T2
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, T, int], T], accumulator: None = None
+    ) -> "Chain[T]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, T], T], accumulator: None = None
+    ) -> "Chain[T]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Callable[[T], T], accumulator: None = None
+    ) -> "Chain[T]": ...
+    @t.overload
+    def reduce_(
+        self: "Chain[t.Iterable[T]]", iteratee: None = None, accumulator: t.Union[T, None] = None
+    ) -> "Chain[T]": ...
+    def reduce_(self, iteratee=None, accumulator=None):
+        return self._wrap(pyd.reduce_)(iteratee, accumulator)
+
+    reduce = reduce_
+
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T3, T2, T], T3], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T3, T2], T3], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Mapping[t.Any, t.Any]]", iteratee: t.Callable[[T3], T3], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Callable[[T2, T2, T], T2],
+        accumulator: None = None,
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Callable[[T2, T2], T2],
+        accumulator: None = None,
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Mapping[t.Any, t.Any]]",
+        iteratee: t.Callable[[T], T],
+        accumulator: None = None,
+    ) -> "Chain[T]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T2, T, int], T2], accumulator: T2
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T2, T], T2], accumulator: T2
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Callable[[T2], T2], accumulator: T2
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, T, int], T], accumulator: None = None
+    ) -> "Chain[T]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, T], T], accumulator: None = None
+    ) -> "Chain[T]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Callable[[T], T], accumulator: None = None
+    ) -> "Chain[T]": ...
+    @t.overload
+    def reduce_right(
+        self: "Chain[t.Iterable[T]]", iteratee: None = None, accumulator: t.Union[T, None] = None
+    ) -> "Chain[T]": ...
+    def reduce_right(self, iteratee=None, accumulator=None):
+        return self._wrap(pyd.reduce_right)(iteratee, accumulator)
+
+    @t.overload
+    def reductions(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Callable[[T3, T2, T], T3],
+        accumulator: T3,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Callable[[T3, T2], T3],
+        accumulator: T3,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Mapping[t.Any, t.Any]]",
+        iteratee: t.Callable[[T3], T3],
+        accumulator: T3,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Callable[[T2, T2, T], T2],
+        accumulator: None = None,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Callable[[T2, T2], T2],
+        accumulator: None = None,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Mapping[t.Any, t.Any]]",
+        iteratee: t.Callable[[T], T],
+        accumulator: None = None,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Callable[[T2, T, int], T2],
+        accumulator: T2,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Callable[[T2, T], T2],
+        accumulator: T2,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Iterable[t.Any]]",
+        iteratee: t.Callable[[T2], T2],
+        accumulator: T2,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Callable[[T, T, int], T],
+        accumulator: None = None,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Callable[[T, T], T],
+        accumulator: None = None,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Iterable[t.Any]]",
+        iteratee: t.Callable[[T], T],
+        accumulator: None = None,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def reductions(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: None = None,
+        accumulator: t.Union[T, None] = None,
+        from_right: bool = False,
+    ) -> "Chain[t.List[T]]": ...
+    def reductions(self, iteratee=None, accumulator=None, from_right=False):
+        return self._wrap(pyd.reductions)(iteratee, accumulator, from_right)
+
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T3, T2, T], T3], accumulator: T3
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T3, T2], T3], accumulator: T3
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Mapping[t.Any, t.Any]]", iteratee: t.Callable[[T3], T3], accumulator: T3
+    ) -> "Chain[t.List[T3]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Callable[[T2, T2, T], T2],
+        accumulator: None = None,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Callable[[T2, T2], T2],
+        accumulator: None = None,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Mapping[t.Any, t.Any]]",
+        iteratee: t.Callable[[T], T],
+        accumulator: None = None,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T2, T, int], T2], accumulator: T2
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T2, T], T2], accumulator: T2
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Callable[[T2], T2], accumulator: T2
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, T, int], T], accumulator: None = None
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, T], T], accumulator: None = None
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Callable[[T], T], accumulator: None = None
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def reductions_right(
+        self: "Chain[t.Iterable[T]]", iteratee: None = None, accumulator: t.Union[T, None] = None
+    ) -> "Chain[t.List[T]]": ...
+    def reductions_right(self, iteratee=None, accumulator=None):
+        return self._wrap(pyd.reductions_right)(iteratee, accumulator)
+
+    @t.overload
+    def reject(
+        self: "Chain[t.Mapping[T, T2]]",
+        predicate: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reject(
+        self: "Chain[t.Mapping[T, T2]]",
+        predicate: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reject(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        predicate: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def reject(
+        self: "Chain[t.Iterable[T]]",
+        predicate: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def reject(
+        self: "Chain[t.Iterable[T]]",
+        predicate: t.Union[t.Callable[[T, int], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def reject(
+        self: "Chain[t.Iterable[T]]",
+        predicate: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+    ) -> "Chain[t.List[T]]": ...
+    def reject(self, predicate=None):
+        return self._wrap(pyd.reject)(predicate)
+
+    def sample(self: "Chain[t.Sequence[T]]") -> "Chain[T]":
+        return self._wrap(pyd.sample)()
+
+    def sample_size(
+        self: "Chain[t.Sequence[T]]", n: t.Union[int, None] = None
+    ) -> "Chain[t.List[T]]":
+        return self._wrap(pyd.sample_size)(n)
+
+    @t.overload
+    def shuffle(self: "Chain[t.Mapping[t.Any, T]]") -> "Chain[t.List[T]]": ...
+    @t.overload
+    def shuffle(self: "Chain[t.Iterable[T]]") -> "Chain[t.List[T]]": ...
+    def shuffle(self):
+        return self._wrap(pyd.shuffle)()
+
+    def size(self: "Chain[t.Sized]") -> "Chain[int]":
+        return self._wrap(pyd.size)()
+
+    def some(
+        self: "Chain[t.Iterable[T]]", predicate: t.Union[t.Callable[[T], t.Any], None] = None
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.some)(predicate)
+
+    @t.overload
+    def sort_by(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+        reverse: bool = False,
+    ) -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def sort_by(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+        reverse: bool = False,
+    ) -> "Chain[t.List[T]]": ...
+    def sort_by(self, iteratee=None, reverse=False):
+        return self._wrap(pyd.sort_by)(iteratee, reverse)
+
+    def after(self: "Chain[t.Callable[P, T]]", n: t.SupportsInt) -> "Chain[After[P, T]]":
+        return self._wrap(pyd.after)(n)
+
+    def ary(self: "Chain[t.Callable[..., T]]", n: t.Union[t.SupportsInt, None]) -> "Chain[Ary[T]]":
+        return self._wrap(pyd.ary)(n)
+
+    def before(self: "Chain[t.Callable[P, T]]", n: t.SupportsInt) -> "Chain[Before[P, T]]":
+        return self._wrap(pyd.before)(n)
+
+    def conjoin(
+        self: "Chain[t.Callable[[T], t.Any]]", *funcs: t.Callable[[T], t.Any]
+    ) -> "Chain[t.Callable[[t.Iterable[T]], bool]]":
+        return self._wrap(pyd.conjoin)(*funcs)
+
+    @t.overload
+    def curry(
+        self: "Chain[t.Callable[[T1], T]]", arity: t.Union[int, None] = None
+    ) -> "Chain[CurryOne[T1, T]]": ...
+    @t.overload
+    def curry(
+        self: "Chain[t.Callable[[T1, T2], T]]", arity: t.Union[int, None] = None
+    ) -> "Chain[CurryTwo[T1, T2, T]]": ...
+    @t.overload
+    def curry(
+        self: "Chain[t.Callable[[T1, T2, T3], T]]", arity: t.Union[int, None] = None
+    ) -> "Chain[CurryThree[T1, T2, T3, T]]": ...
+    @t.overload
+    def curry(
+        self: "Chain[t.Callable[[T1, T2, T3, T4], T]]", arity: t.Union[int, None] = None
+    ) -> "Chain[CurryFour[T1, T2, T3, T4, T]]": ...
+    @t.overload
+    def curry(
+        self: "Chain[t.Callable[[T1, T2, T3, T4, T5], T]]", arity: t.Union[int, None] = None
+    ) -> "Chain[CurryFive[T1, T2, T3, T4, T5, T]]": ...
+    def curry(self, arity=None):
+        return self._wrap(pyd.curry)(arity)
+
+    @t.overload
+    def curry_right(
+        self: "Chain[t.Callable[[T1], T]]", arity: t.Union[int, None] = None
+    ) -> "Chain[CurryRightOne[T1, T]]": ...
+    @t.overload
+    def curry_right(
+        self: "Chain[t.Callable[[T1, T2], T]]", arity: t.Union[int, None] = None
+    ) -> "Chain[CurryRightTwo[T2, T1, T]]": ...
+    @t.overload
+    def curry_right(
+        self: "Chain[t.Callable[[T1, T2, T3], T]]", arity: t.Union[int, None] = None
+    ) -> "Chain[CurryRightThree[T3, T2, T1, T]]": ...
+    @t.overload
+    def curry_right(
+        self: "Chain[t.Callable[[T1, T2, T3, T4], T]]", arity: t.Union[int, None] = None
+    ) -> "Chain[CurryRightFour[T4, T3, T2, T1, T]]": ...
+    @t.overload
+    def curry_right(
+        self: "Chain[t.Callable[[T1, T2, T3, T4, T5], T]]",
+    ) -> "Chain[CurryRightFive[T5, T4, T3, T2, T1, T]]": ...
+    def curry_right(self, arity=None):
+        return self._wrap(pyd.curry_right)(arity)
+
+    def debounce(
+        self: "Chain[t.Callable[P, T]]", wait: int, max_wait: t.Union[int, Literal[False]] = False
+    ) -> "Chain[Debounce[P, T]]":
+        return self._wrap(pyd.debounce)(wait, max_wait)
+
+    def delay(
+        self: "Chain[t.Callable[P, T]]", wait: int, *args: "P.args", **kwargs: "P.kwargs"
+    ) -> "Chain[T]":
+        return self._wrap(pyd.delay)(wait, *args, **kwargs)
+
+    def disjoin(
+        self: "Chain[t.Callable[[T], t.Any]]", *funcs: t.Callable[[T], t.Any]
+    ) -> "Chain[Disjoin[T]]":
+        return self._wrap(pyd.disjoin)(*funcs)
+
+    @t.overload
+    def flip(
+        self: "Chain[t.Callable[[T1, T2, T3, T4, T5], T]]",
+    ) -> "Chain[t.Callable[[T5, T4, T3, T2, T1], T]]": ...
+    @t.overload
+    def flip(
+        self: "Chain[t.Callable[[T1, T2, T3, T4], T]]",
+    ) -> "Chain[t.Callable[[T4, T3, T2, T1], T]]": ...
+    @t.overload
+    def flip(
+        self: "Chain[t.Callable[[T1, T2, T3], T]]",
+    ) -> "Chain[t.Callable[[T3, T2, T1], T]]": ...
+    @t.overload
+    def flip(self: "Chain[t.Callable[[T1, T2], T]]") -> "Chain[t.Callable[[T2, T1], T]]": ...
+    @t.overload
+    def flip(self: "Chain[t.Callable[[T1], T]]") -> "Chain[t.Callable[[T1], T]]": ...
+    def flip(self: "Chain[t.Callable[..., t.Any]]") -> "Chain[t.Callable[..., t.Any]]":
+        return self._wrap(pyd.flip)()
+
+    @t.overload
+    def flow(
+        self: "Chain[t.Callable[P, T2]]",
+        func2: t.Callable[[T2], T3],
+        func3: t.Callable[[T3], T4],
+        func4: t.Callable[[T4], T5],
+        func5: t.Callable[[T5], T],
+    ) -> "Chain[Flow[P, T]]": ...
+    @t.overload
+    def flow(
+        self: "Chain[t.Callable[P, T2]]",
+        func2: t.Callable[[T2], T3],
+        func3: t.Callable[[T3], T4],
+        func4: t.Callable[[T4], T],
+    ) -> "Chain[Flow[P, T]]": ...
+    @t.overload
+    def flow(
+        self: "Chain[t.Callable[P, T2]]", func2: t.Callable[[T2], T3], func3: t.Callable[[T3], T]
+    ) -> "Chain[Flow[P, T]]": ...
+    @t.overload
+    def flow(
+        self: "Chain[t.Callable[P, T2]]", func2: t.Callable[[T2], T]
+    ) -> "Chain[Flow[P, T]]": ...
+    @t.overload
+    def flow(self: "Chain[t.Callable[P, T]]") -> "Chain[Flow[P, T]]": ...
+    def flow(self, *funcs):
+        return self._wrap(pyd.flow)(*funcs)
+
+    @t.overload
+    def flow_right(
+        self: "Chain[t.Callable[[T4], T]]",
+        func4: t.Callable[[T3], T4],
+        func3: t.Callable[[T2], T3],
+        func2: t.Callable[[T1], T2],
+        func1: t.Callable[P, T1],
+    ) -> "Chain[Flow[P, T]]": ...
+    @t.overload
+    def flow_right(
+        self: "Chain[t.Callable[[T3], T]]",
+        func3: t.Callable[[T2], T3],
+        func2: t.Callable[[T1], T2],
+        func1: t.Callable[P, T1],
+    ) -> "Chain[Flow[P, T]]": ...
+    @t.overload
+    def flow_right(
+        self: "Chain[t.Callable[[T2], T]]", func2: t.Callable[[T1], T2], func1: t.Callable[P, T1]
+    ) -> "Chain[Flow[P, T]]": ...
+    @t.overload
+    def flow_right(
+        self: "Chain[t.Callable[[T1], T]]", func1: t.Callable[P, T1]
+    ) -> "Chain[Flow[P, T]]": ...
+    @t.overload
+    def flow_right(self: "Chain[t.Callable[P, T]]") -> "Chain[Flow[P, T]]": ...
+    def flow_right(self, *funcs):
+        return self._wrap(pyd.flow_right)(*funcs)
+
+    def iterated(self: "Chain[t.Callable[[T], T]]") -> "Chain[Iterated[T]]":
+        return self._wrap(pyd.iterated)()
+
+    def juxtapose(
+        self: "Chain[t.Callable[P, T]]", *funcs: t.Callable[P, T]
+    ) -> "Chain[Juxtapose[P, T]]":
+        return self._wrap(pyd.juxtapose)(*funcs)
+
+    def negate(self: "Chain[t.Callable[P, t.Any]]") -> "Chain[Negate[P]]":
+        return self._wrap(pyd.negate)()
+
+    def once(self: "Chain[t.Callable[P, T]]") -> "Chain[Once[P, T]]":
+        return self._wrap(pyd.once)()
+
+    @t.overload
+    def over_args(
+        self: "Chain[t.Callable[[T1, T2, T3, T4, T5], T]]",
+        transform_one: t.Callable[[T1], T1],
+        transform_two: t.Callable[[T2], T2],
+        transform_three: t.Callable[[T3], T3],
+        transform_four: t.Callable[[T4], T4],
+        transform_five: t.Callable[[T5], T5],
+    ) -> "Chain[t.Callable[[T1, T2, T3, T4, T5], T]]": ...
+    @t.overload
+    def over_args(
+        self: "Chain[t.Callable[[T1, T2, T3, T4], T]]",
+        transform_one: t.Callable[[T1], T1],
+        transform_two: t.Callable[[T2], T2],
+        transform_three: t.Callable[[T3], T3],
+        transform_four: t.Callable[[T4], T4],
+    ) -> "Chain[t.Callable[[T1, T2, T3, T4], T]]": ...
+    @t.overload
+    def over_args(
+        self: "Chain[t.Callable[[T1, T2, T3], T]]",
+        transform_one: t.Callable[[T1], T1],
+        transform_two: t.Callable[[T2], T2],
+        transform_three: t.Callable[[T3], T3],
+    ) -> "Chain[t.Callable[[T1, T2, T3], T]]": ...
+    @t.overload
+    def over_args(
+        self: "Chain[t.Callable[[T1, T2], T]]",
+        transform_one: t.Callable[[T1], T1],
+        transform_two: t.Callable[[T2], T2],
+    ) -> "Chain[t.Callable[[T1, T2], T]]": ...
+    @t.overload
+    def over_args(
+        self: "Chain[t.Callable[[T1], T]]", transform_one: t.Callable[[T1], T1]
+    ) -> "Chain[t.Callable[[T1], T]]": ...
+    def over_args(self, *transforms):
+        return self._wrap(pyd.over_args)(*transforms)
+
+    def partial(
+        self: "Chain[t.Callable[..., T]]", *args: t.Any, **kwargs: t.Any
+    ) -> "Chain[Partial[T]]":
+        return self._wrap(pyd.partial)(*args, **kwargs)
+
+    def partial_right(
+        self: "Chain[t.Callable[..., T]]", *args: t.Any, **kwargs: t.Any
+    ) -> "Chain[Partial[T]]":
+        return self._wrap(pyd.partial_right)(*args, **kwargs)
+
+    def rearg(self: "Chain[t.Callable[P, T]]", *indexes: int) -> "Chain[Rearg[P, T]]":
+        return self._wrap(pyd.rearg)(*indexes)
+
+    def spread(self: "Chain[t.Callable[..., T]]") -> "Chain[Spread[T]]":
+        return self._wrap(pyd.spread)()
+
+    def throttle(self: "Chain[t.Callable[P, T]]", wait: int) -> "Chain[Throttle[P, T]]":
+        return self._wrap(pyd.throttle)(wait)
+
+    def unary(self: "Chain[t.Callable[..., T]]") -> "Chain[Ary[T]]":
+        return self._wrap(pyd.unary)()
+
+    def wrap(self: "Chain[T1]", func: t.Callable[Concatenate[T1, P], T]) -> "Chain[Partial[T]]":
+        return self._wrap(pyd.wrap)(func)
+
+    @t.overload
+    def add(self: "Chain['SupportsAdd[T, T2]']", b: T) -> "Chain[T2]": ...
+    @t.overload
+    def add(self: "Chain[T]", b: "SupportsAdd[T, T2]") -> "Chain[T2]": ...
+    def add(self, b):
+        return self._wrap(pyd.add)(b)
+
+    @t.overload
+    def sum_(self: "Chain[t.Mapping[t.Any, 'SupportsAdd[int, T]']]") -> "Chain[T]": ...
+    @t.overload
+    def sum_(self: "Chain[t.Iterable['SupportsAdd[int, T]']]") -> "Chain[T]": ...
+    def sum_(self):
+        return self._wrap(pyd.sum_)()
+
+    sum = sum_
+
+    @t.overload
+    def sum_by(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Callable[[T2, T, t.Dict[T, T2]], "SupportsAdd[int, T3]"],
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def sum_by(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], "SupportsAdd[int, T3]"]
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def sum_by(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T2], "SupportsAdd[int, T3]"]
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def sum_by(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Callable[[T, int, t.List[T]], "SupportsAdd[int, T2]"],
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def sum_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], "SupportsAdd[int, T2]"]
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def sum_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], "SupportsAdd[int, T2]"]
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def sum_by(
+        self: "Chain[t.Mapping[t.Any, 'SupportsAdd[int, T]']]", iteratee: None = None
+    ) -> "Chain[T]": ...
+    @t.overload
+    def sum_by(
+        self: "Chain[t.Iterable['SupportsAdd[int, T]']]", iteratee: None = None
+    ) -> "Chain[T]": ...
+    def sum_by(self, iteratee=None):
+        return self._wrap(pyd.sum_by)(iteratee)
+
+    @t.overload
+    def mean(self: "Chain[t.Mapping[t.Any, 'SupportsAdd[int, t.Any]']]") -> "Chain[float]": ...
+    @t.overload
+    def mean(self: "Chain[t.Iterable['SupportsAdd[int, t.Any]']]") -> "Chain[float]": ...
+    def mean(self):
+        return self._wrap(pyd.mean)()
+
+    @t.overload
+    def mean_by(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Callable[[T2, T, t.Dict[T, T2]], "SupportsAdd[int, t.Any]"],
+    ) -> "Chain[float]": ...
+    @t.overload
+    def mean_by(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], "SupportsAdd[int, t.Any]"]
+    ) -> "Chain[float]": ...
+    @t.overload
+    def mean_by(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T2], "SupportsAdd[int, t.Any]"]
+    ) -> "Chain[float]": ...
+    @t.overload
+    def mean_by(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Callable[[T, int, t.List[T]], "SupportsAdd[int, t.Any]"],
+    ) -> "Chain[float]": ...
+    @t.overload
+    def mean_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], "SupportsAdd[int, t.Any]"]
+    ) -> "Chain[float]": ...
+    @t.overload
+    def mean_by(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], "SupportsAdd[int, t.Any]"]
+    ) -> "Chain[float]": ...
+    @t.overload
+    def mean_by(
+        self: "Chain[t.Mapping[t.Any, 'SupportsAdd[int, t.Any]']]", iteratee: None = None
+    ) -> "Chain[float]": ...
+    @t.overload
+    def mean_by(
+        self: "Chain[t.Iterable['SupportsAdd[int, t.Any]']]", iteratee: None = None
+    ) -> "Chain[float]": ...
+    def mean_by(self, iteratee=None):
+        return self._wrap(pyd.mean_by)(iteratee)
+
+    def ceil(self: "Chain[NumberT]", precision: int = 0) -> "Chain[float]":
+        return self._wrap(pyd.ceil)(precision)
+
+    def clamp(
+        self: "Chain[NumT]", lower: NumT2, upper: t.Union[NumT3, None] = None
+    ) -> "Chain[t.Union[NumT, NumT2, NumT3]]":
+        return self._wrap(pyd.clamp)(lower, upper)
+
+    def divide(
+        self: "Chain[t.Union[NumberT, None]]", divisor: t.Union[NumberT, None]
+    ) -> "Chain[float]":
+        return self._wrap(pyd.divide)(divisor)
+
+    def floor(self: "Chain[NumberT]", precision: int = 0) -> "Chain[float]":
+        return self._wrap(pyd.floor)(precision)
+
+    @t.overload
+    def max_(
+        self: "Chain[t.Mapping[t.Any, 'SupportsRichComparisonT']]", default: Unset = UNSET
+    ) -> "Chain['SupportsRichComparisonT']": ...
+    @t.overload
+    def max_(
+        self: "Chain[t.Mapping[t.Any, 'SupportsRichComparisonT']]", default: T
+    ) -> "Chain[t.Union['SupportsRichComparisonT', T]]": ...
+    @t.overload
+    def max_(
+        self: "Chain[t.Iterable['SupportsRichComparisonT']]", default: Unset = UNSET
+    ) -> "Chain['SupportsRichComparisonT']": ...
+    @t.overload
+    def max_(
+        self: "Chain[t.Iterable['SupportsRichComparisonT']]", default: T
+    ) -> "Chain[t.Union['SupportsRichComparisonT', T]]": ...
+    def max_(self, default=UNSET):
+        return self._wrap(pyd.max_)(default)
+
+    max = max_
+
+    @t.overload
+    def max_by(
+        self: "Chain[t.Mapping[t.Any, 'SupportsRichComparisonT']]",
+        iteratee: None = None,
+        default: Unset = UNSET,
+    ) -> "Chain['SupportsRichComparisonT']": ...
+    @t.overload
+    def max_by(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+        default: Unset = UNSET,
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def max_by(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+        *,
+        default: T,
+    ) -> "Chain[t.Union[T2, T]]": ...
+    @t.overload
+    def max_by(
+        self: "Chain[t.Mapping[t.Any, 'SupportsRichComparisonT']]",
+        iteratee: None = None,
+        *,
+        default: T,
+    ) -> "Chain[t.Union['SupportsRichComparisonT', T]]": ...
+    @t.overload
+    def max_by(
+        self: "Chain[t.Iterable['SupportsRichComparisonT']]",
+        iteratee: None = None,
+        default: Unset = UNSET,
+    ) -> "Chain['SupportsRichComparisonT']": ...
+    @t.overload
+    def max_by(
+        self: "Chain[t.Iterable[T2]]",
+        iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+        default: Unset = UNSET,
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def max_by(
+        self: "Chain[t.Iterable[T2]]",
+        iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+        *,
+        default: T,
+    ) -> "Chain[t.Union[T2, T]]": ...
+    @t.overload
+    def max_by(
+        self: "Chain[t.Iterable['SupportsRichComparisonT']]", iteratee: None = None, *, default: T
+    ) -> "Chain[t.Union['SupportsRichComparisonT', T]]": ...
+    @t.overload
+    def max_by(
+        self: "Chain[t.Iterable[T]]", iteratee: IterateeObjT, default: Unset = UNSET
+    ) -> "Chain[T]": ...
+    @t.overload
+    def max_by(
+        self: "Chain[t.Iterable[T]]", iteratee: IterateeObjT, default: T2
+    ) -> "Chain[t.Union[T, T2]]": ...
+    def max_by(self, iteratee=None, default=UNSET):
+        return self._wrap(pyd.max_by)(iteratee, default)
+
+    @t.overload
+    def median(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T, t.Dict[T, T2]], NumberT]
+    ) -> "Chain[t.Union[float, int]]": ...
+    @t.overload
+    def median(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], NumberT]
+    ) -> "Chain[t.Union[float, int]]": ...
+    @t.overload
+    def median(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T2], NumberT]
+    ) -> "Chain[t.Union[float, int]]": ...
+    @t.overload
+    def median(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], NumberT]
+    ) -> "Chain[t.Union[float, int]]": ...
+    @t.overload
+    def median(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], NumberT]
+    ) -> "Chain[t.Union[float, int]]": ...
+    @t.overload
+    def median(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], NumberT]
+    ) -> "Chain[t.Union[float, int]]": ...
+    @t.overload
+    def median(
+        self: "Chain[t.Iterable[NumberT]]", iteratee: None = None
+    ) -> "Chain[t.Union[float, int]]": ...
+    def median(self, iteratee=None):
+        return self._wrap(pyd.median)(iteratee)
+
+    @t.overload
+    def min_(
+        self: "Chain[t.Mapping[t.Any, 'SupportsRichComparisonT']]", default: Unset = UNSET
+    ) -> "Chain['SupportsRichComparisonT']": ...
+    @t.overload
+    def min_(
+        self: "Chain[t.Mapping[t.Any, 'SupportsRichComparisonT']]", default: T
+    ) -> "Chain[t.Union['SupportsRichComparisonT', T]]": ...
+    @t.overload
+    def min_(
+        self: "Chain[t.Iterable['SupportsRichComparisonT']]", default: Unset = UNSET
+    ) -> "Chain['SupportsRichComparisonT']": ...
+    @t.overload
+    def min_(
+        self: "Chain[t.Iterable['SupportsRichComparisonT']]", default: T
+    ) -> "Chain[t.Union['SupportsRichComparisonT', T]]": ...
+    def min_(self, default=UNSET):
+        return self._wrap(pyd.min_)(default)
+
+    min = min_
+
+    @t.overload
+    def min_by(
+        self: "Chain[t.Mapping[t.Any, 'SupportsRichComparisonT']]",
+        iteratee: None = None,
+        default: Unset = UNSET,
+    ) -> "Chain['SupportsRichComparisonT']": ...
+    @t.overload
+    def min_by(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+        default: Unset = UNSET,
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def min_by(
+        self: "Chain[t.Mapping[t.Any, T2]]",
+        iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+        *,
+        default: T,
+    ) -> "Chain[t.Union[T2, T]]": ...
+    @t.overload
+    def min_by(
+        self: "Chain[t.Mapping[t.Any, 'SupportsRichComparisonT']]",
+        iteratee: None = None,
+        *,
+        default: T,
+    ) -> "Chain[t.Union['SupportsRichComparisonT', T]]": ...
+    @t.overload
+    def min_by(
+        self: "Chain[t.Iterable['SupportsRichComparisonT']]",
+        iteratee: None = None,
+        default: Unset = UNSET,
+    ) -> "Chain['SupportsRichComparisonT']": ...
+    @t.overload
+    def min_by(
+        self: "Chain[t.Iterable[T2]]",
+        iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+        default: Unset = UNSET,
+    ) -> "Chain[T2]": ...
+    @t.overload
+    def min_by(
+        self: "Chain[t.Iterable[T2]]",
+        iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+        *,
+        default: T,
+    ) -> "Chain[t.Union[T2, T]]": ...
+    @t.overload
+    def min_by(
+        self: "Chain[t.Iterable['SupportsRichComparisonT']]", iteratee: None = None, *, default: T
+    ) -> "Chain[t.Union['SupportsRichComparisonT', T]]": ...
+    @t.overload
+    def min_by(
+        self: "Chain[t.Iterable[T]]", iteratee: IterateeObjT, default: Unset = UNSET
+    ) -> "Chain[T]": ...
+    @t.overload
+    def min_by(
+        self: "Chain[t.Iterable[T]]", iteratee: IterateeObjT, default: T2
+    ) -> "Chain[t.Union[T, T2]]": ...
+    def min_by(self, iteratee=None, default=UNSET):
+        return self._wrap(pyd.min_by)(iteratee, default)
+
+    def moving_mean(
+        self: "Chain[t.Sequence['SupportsAdd[int, t.Any]']]", size: t.SupportsInt
+    ) -> "Chain[t.List[float]]":
+        return self._wrap(pyd.moving_mean)(size)
+
+    @t.overload
+    def multiply(self: "Chain[SupportsMul[int, T2]]", multiplicand: None) -> "Chain[T2]": ...
+    @t.overload
+    def multiply(self: "Chain[None]", multiplicand: SupportsMul[int, T2]) -> "Chain[T2]": ...
+    @t.overload
+    def multiply(self: "Chain[None]", multiplicand: None) -> "Chain[int]": ...
+    @t.overload
+    def multiply(self: "Chain[SupportsMul[T, T2]]", multiplicand: T) -> "Chain[T2]": ...
+    @t.overload
+    def multiply(self: "Chain[T]", multiplicand: SupportsMul[T, T2]) -> "Chain[T2]": ...
+    def multiply(self, multiplicand):
+        return self._wrap(pyd.multiply)(multiplicand)
+
+    @t.overload
+    def power(self: "Chain[int]", n: int) -> "Chain[t.Union[int, float]]": ...
+    @t.overload
+    def power(self: "Chain[float]", n: t.Union[int, float]) -> "Chain[float]": ...
+    @t.overload
+    def power(self: "Chain[t.List[int]]", n: int) -> "Chain[t.List[t.Union[int, float]]]": ...
+    @t.overload
+    def power(
+        self: "Chain[t.List[float]]", n: t.List[t.Union[int, float]]
+    ) -> "Chain[t.List[float]]": ...
+    def power(self, n):
+        return self._wrap(pyd.power)(n)
+
+    @t.overload
+    def round_(
+        self: "Chain[t.List[SupportsRound[NumberT]]]", precision: int = 0
+    ) -> "Chain[t.List[float]]": ...
+    @t.overload
+    def round_(self: "Chain[SupportsRound[NumberT]]", precision: int = 0) -> "Chain[float]": ...
+    def round_(self, precision=0):
+        return self._wrap(pyd.round_)(precision)
+
+    round = round_
+
+    @t.overload
+    def scale(
+        self: "Chain[t.Iterable['Decimal']]", maximum: "Decimal"
+    ) -> "Chain[t.List['Decimal']]": ...
+    @t.overload
+    def scale(
+        self: "Chain[t.Iterable[NumberNoDecimalT]]", maximum: NumberNoDecimalT
+    ) -> "Chain[t.List[float]]": ...
+    @t.overload
+    def scale(self: "Chain[t.Iterable[NumberT]]", maximum: int = 1) -> "Chain[t.List[float]]": ...
+    def scale(self, maximum: NumberT = 1):
+        return self._wrap(pyd.scale)(maximum)
+
+    @t.overload
+    def slope(
+        self: "Chain[t.Union[t.Tuple['Decimal', 'Decimal'], t.List['Decimal']]]",
+        point2: t.Union[t.Tuple["Decimal", "Decimal"], t.List["Decimal"]],
+    ) -> "Chain['Decimal']": ...
+    @t.overload
+    def slope(
+        self: "Chain[t.Union[t.Tuple[NumberNoDecimalT, NumberNoDecimalT], t.List[NumberNoDecimalT]]]",
+        point2: t.Union[t.Tuple[NumberNoDecimalT, NumberNoDecimalT], t.List[NumberNoDecimalT]],
+    ) -> "Chain[float]": ...
+    def slope(self, point2):
+        return self._wrap(pyd.slope)(point2)
+
+    def std_deviation(self: "Chain[t.List[NumberT]]") -> "Chain[float]":
+        return self._wrap(pyd.std_deviation)()
+
+    @t.overload
+    def subtract(self: "Chain['SupportsSub[T, T2]']", subtrahend: T) -> "Chain[T2]": ...
+    @t.overload
+    def subtract(self: "Chain[T]", subtrahend: "SupportsSub[T, T2]") -> "Chain[T2]": ...
+    def subtract(self, subtrahend):
+        return self._wrap(pyd.subtract)(subtrahend)
+
+    def transpose(self: "Chain[t.Iterable[t.Iterable[T]]]") -> "Chain[t.List[t.List[T]]]":
+        return self._wrap(pyd.transpose)()
+
+    @t.overload
+    def variance(self: "Chain[t.Mapping[t.Any, 'SupportsAdd[int, t.Any]']]") -> "Chain[float]": ...
+    @t.overload
+    def variance(self: "Chain[t.Iterable['SupportsAdd[int, t.Any]']]") -> "Chain[float]": ...
+    def variance(self):
+        return self._wrap(pyd.variance)()
+
+    @t.overload
+    def zscore(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T, t.Dict[T, T2]], NumberT]
+    ) -> "Chain[t.List[float]]": ...
+    @t.overload
+    def zscore(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], NumberT]
+    ) -> "Chain[t.List[float]]": ...
+    @t.overload
+    def zscore(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T2], NumberT]
+    ) -> "Chain[t.List[float]]": ...
+    @t.overload
+    def zscore(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], NumberT]
+    ) -> "Chain[t.List[float]]": ...
+    @t.overload
+    def zscore(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], NumberT]
+    ) -> "Chain[t.List[float]]": ...
+    @t.overload
+    def zscore(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], NumberT]
+    ) -> "Chain[t.List[float]]": ...
+    @t.overload
+    def zscore(
+        self: "Chain[t.Iterable[NumberT]]", iteratee: None = None
+    ) -> "Chain[t.List[float]]": ...
+    def zscore(self, iteratee=None):
+        return self._wrap(pyd.zscore)(iteratee)
+
+    @t.overload
+    def assign(
+        self: "Chain[t.Mapping[T, T2]]", *sources: t.Mapping[T3, T4]
+    ) -> "Chain[t.Dict[t.Union[T, T3], t.Union[T2, T4]]]": ...
+    @t.overload
+    def assign(
+        self: "Chain[t.Union[t.Tuple[T, ...], t.List[T]]]", *sources: t.Mapping[int, T2]
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    def assign(self, *sources) -> "Chain[t.Union[t.List[t.Any], t.Dict[t.Any, t.Any]]]":
+        return self._wrap(pyd.assign)(*sources)
+
+    @t.overload
+    def assign_with(
+        self: "Chain[t.Mapping[T, T2]]",
+        *sources: t.Mapping[T3, t.Any],
+        customizer: t.Callable[[t.Union[T2, None]], T5],
+    ) -> "Chain[t.Dict[t.Union[T, T3], t.Union[T2, T5]]]": ...
+    @t.overload
+    def assign_with(
+        self: "Chain[t.Mapping[T, T2]]",
+        *sources: t.Mapping[T3, T4],
+        customizer: t.Callable[[t.Union[T2, None], T4], T5],
+    ) -> "Chain[t.Dict[t.Union[T, T3], t.Union[T2, T5]]]": ...
+    @t.overload
+    def assign_with(
+        self: "Chain[t.Mapping[T, T2]]",
+        *sources: t.Mapping[T3, T4],
+        customizer: t.Callable[[t.Union[T2, None], T4, T3], T5],
+    ) -> "Chain[t.Dict[t.Union[T, T3], t.Union[T2, T5]]]": ...
+    @t.overload
+    def assign_with(
+        self: "Chain[t.Mapping[T, T2]]",
+        *sources: t.Mapping[T3, T4],
+        customizer: t.Callable[[t.Union[T2, None], T4, T3, t.Dict[T, T2]], T5],
+    ) -> "Chain[t.Dict[t.Union[T, T3], t.Union[T2, T5]]]": ...
+    @t.overload
+    def assign_with(
+        self: "Chain[t.Mapping[T, T2]]",
+        *sources: t.Mapping[T3, T4],
+        customizer: t.Callable[[t.Union[T2, None], T4, T3, t.Dict[T, T2], t.Dict[T3, T4]], T5],
+    ) -> "Chain[t.Dict[t.Union[T, T3], t.Union[T2, T5]]]": ...
+    @t.overload
+    def assign_with(
+        self: "Chain[t.Mapping[T, T2]]", *sources: t.Mapping[T3, T4], customizer: None = None
+    ) -> "Chain[t.Dict[t.Union[T, T3], t.Union[T2, T4]]]": ...
+    def assign_with(self, *sources, customizer=None):
+        return self._wrap(pyd.assign_with)(*sources, customizer=customizer)
+
+    @t.overload
+    def callables(
+        self: "Chain[t.Mapping['SupportsRichComparisonT', t.Any]]",
+    ) -> "Chain[t.List['SupportsRichComparisonT']]": ...
+    @t.overload
+    def callables(self: "Chain[t.Iterable[T]]") -> "Chain[t.List[T]]": ...
+    def callables(self):
+        return self._wrap(pyd.callables)()
+
+    def clone(self: "Chain[T]") -> "Chain[T]":
+        return self._wrap(pyd.clone)()
+
+    @t.overload
+    def clone_with(
+        self: "Chain[t.Mapping[T, T2]]", customizer: t.Callable[[T2, T, t.Mapping[T, T2]], T3]
+    ) -> "Chain[t.Dict[T, t.Union[T2, T3]]]": ...
+    @t.overload
+    def clone_with(
+        self: "Chain[t.Mapping[T, T2]]", customizer: t.Callable[[T2, T], T3]
+    ) -> "Chain[t.Dict[T, t.Union[T2, T3]]]": ...
+    @t.overload
+    def clone_with(
+        self: "Chain[t.Mapping[T, T2]]", customizer: t.Callable[[T2], T3]
+    ) -> "Chain[t.Dict[T, t.Union[T2, T3]]]": ...
+    @t.overload
+    def clone_with(
+        self: "Chain[t.List[T]]", customizer: t.Callable[[T, int, t.List[T]], T2]
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    @t.overload
+    def clone_with(
+        self: "Chain[t.List[T]]", customizer: t.Callable[[T, int], T2]
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    @t.overload
+    def clone_with(
+        self: "Chain[t.List[T]]", customizer: t.Callable[[T], T2]
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    @t.overload
+    def clone_with(self: "Chain[T]", customizer: None = None) -> "Chain[T]": ...
+    @t.overload
+    def clone_with(self: "Chain[t.Any]", customizer: t.Callable[..., t.Any]) -> "Chain[t.Any]": ...
+    def clone_with(self, customizer=None):
+        return self._wrap(pyd.clone_with)(customizer)
+
+    def clone_deep(self: "Chain[T]") -> "Chain[T]":
+        return self._wrap(pyd.clone_deep)()
+
+    @t.overload
+    def clone_deep_with(
+        self: "Chain[t.Mapping[T, T2]]", customizer: t.Callable[[T2, T, t.Mapping[T, T2]], T3]
+    ) -> "Chain[t.Dict[T, t.Union[T2, T3]]]": ...
+    @t.overload
+    def clone_deep_with(
+        self: "Chain[t.Mapping[T, T2]]", customizer: t.Callable[[T2, T], T3]
+    ) -> "Chain[t.Dict[T, t.Union[T2, T3]]]": ...
+    @t.overload
+    def clone_deep_with(
+        self: "Chain[t.Mapping[T, T2]]", customizer: t.Callable[[T2], T3]
+    ) -> "Chain[t.Dict[T, t.Union[T2, T3]]]": ...
+    @t.overload
+    def clone_deep_with(
+        self: "Chain[t.List[T]]", customizer: t.Callable[[T, int, t.List[T]], T2]
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    @t.overload
+    def clone_deep_with(
+        self: "Chain[t.List[T]]", customizer: t.Callable[[T, int], T2]
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    @t.overload
+    def clone_deep_with(
+        self: "Chain[t.List[T]]", customizer: t.Callable[[T], T2]
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    @t.overload
+    def clone_deep_with(self: "Chain[T]", customizer: None = None) -> "Chain[T]": ...
+    @t.overload
+    def clone_deep_with(
+        self: "Chain[t.Any]", customizer: t.Callable[..., t.Any]
+    ) -> "Chain[t.Any]": ...
+    def clone_deep_with(self, customizer=None):
+        return self._wrap(pyd.clone_deep_with)(customizer)
+
+    def defaults(
+        self: "Chain[t.Dict[T, T2]]", *sources: t.Dict[T3, T4]
+    ) -> "Chain[t.Dict[t.Union[T, T3], t.Union[T2, T4]]]":
+        return self._wrap(pyd.defaults)(*sources)
+
+    def defaults_deep(
+        self: "Chain[t.Dict[T, T2]]", *sources: t.Dict[T3, T4]
+    ) -> "Chain[t.Dict[t.Union[T, T3], t.Union[T2, T4]]]":
+        return self._wrap(pyd.defaults_deep)(*sources)
+
+    @t.overload
+    def find_key(
+        self: "Chain[t.Mapping[T, T2]]", predicate: t.Callable[[T2, T, t.Dict[T, T2]], t.Any]
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find_key(
+        self: "Chain[t.Mapping[T, T2]]", predicate: t.Callable[[T2, T], t.Any]
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find_key(
+        self: "Chain[t.Mapping[T, T2]]", predicate: t.Callable[[T2], t.Any]
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find_key(
+        self: "Chain[t.Mapping[T, t.Any]]", predicate: None = None
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find_key(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[t.Union[int, None]]": ...
+    @t.overload
+    def find_key(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.Union[int, None]]": ...
+    @t.overload
+    def find_key(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], t.Any]
+    ) -> "Chain[t.Union[int, None]]": ...
+    @t.overload
+    def find_key(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: None = None
+    ) -> "Chain[t.Union[int, None]]": ...
+    def find_key(self, predicate=None):
+        return self._wrap(pyd.find_key)(predicate)
+
+    @t.overload
+    def find_last_key(
+        self: "Chain[t.Mapping[T, T2]]", predicate: t.Callable[[T2, T, t.Dict[T, T2]], t.Any]
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find_last_key(
+        self: "Chain[t.Mapping[T, T2]]", predicate: t.Callable[[T2, T], t.Any]
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find_last_key(
+        self: "Chain[t.Mapping[T, T2]]", predicate: t.Callable[[T2], t.Any]
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find_last_key(
+        self: "Chain[t.Mapping[T, t.Any]]", predicate: None = None
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def find_last_key(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[t.Union[int, None]]": ...
+    @t.overload
+    def find_last_key(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.Union[int, None]]": ...
+    @t.overload
+    def find_last_key(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], t.Any]
+    ) -> "Chain[t.Union[int, None]]": ...
+    @t.overload
+    def find_last_key(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: None = None
+    ) -> "Chain[t.Union[int, None]]": ...
+    def find_last_key(self, predicate=None):
+        return self._wrap(pyd.find_last_key)(predicate)
+
+    @t.overload
+    def for_in(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T, t.Dict[T, T2]], t.Any]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_in(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], t.Any]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_in(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2], t.Any]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_in(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: None = None
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_in(
+        self: "Chain[t.Sequence[T]]", iteratee: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def for_in(
+        self: "Chain[t.Sequence[T]]", iteratee: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def for_in(
+        self: "Chain[t.Sequence[T]]", iteratee: t.Callable[[T], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def for_in(self: "Chain[t.Sequence[T]]", iteratee: None = None) -> "Chain[t.List[T]]": ...
+    def for_in(self, iteratee=None):
+        return self._wrap(pyd.for_in)(iteratee)
+
+    @t.overload
+    def for_in_right(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T, t.Dict[T, T2]], t.Any]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_in_right(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], t.Any]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_in_right(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2], t.Any]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_in_right(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: None = None
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def for_in_right(
+        self: "Chain[t.Sequence[T]]", iteratee: t.Callable[[T, int, t.List[T]], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def for_in_right(
+        self: "Chain[t.Sequence[T]]", iteratee: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def for_in_right(
+        self: "Chain[t.Sequence[T]]", iteratee: t.Callable[[T], t.Any]
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def for_in_right(self: "Chain[t.Sequence[T]]", iteratee: None = None) -> "Chain[t.List[T]]": ...
+    def for_in_right(self, iteratee=None):
+        return self._wrap(pyd.for_in_right)(iteratee)
+
+    @t.overload
+    def get(self: "Chain[t.List[T]]", path: int, default: T2) -> "Chain[t.Union[T, T2]]": ...
+    @t.overload
+    def get(
+        self: "Chain[t.List[T]]", path: int, default: None = None
+    ) -> "Chain[t.Union[T, None]]": ...
+    @t.overload
+    def get(self: "Chain[t.Any]", path: PathT, default: t.Any = None) -> "Chain[t.Any]": ...
+    def get(self: "Chain[t.Any]", path: PathT, default: t.Any = None) -> "Chain[t.Any]":
+        return self._wrap(pyd.get)(path, default)
+
+    def has(self: "Chain[t.Any]", path: PathT) -> "Chain[bool]":
+        return self._wrap(pyd.has)(path)
+
+    @t.overload
+    def invert(self: "Chain[t.Mapping[T, T2]]") -> "Chain[t.Dict[T2, T]]": ...
+    @t.overload
+    def invert(self: "Chain[t.Union[t.Iterator[T], t.Sequence[T]]]") -> "Chain[t.Dict[T, int]]": ...
+    def invert(self):
+        return self._wrap(pyd.invert)()
+
+    @t.overload
+    def invert_by(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2], T3]
+    ) -> "Chain[t.Dict[T3, t.List[T]]]": ...
+    @t.overload
+    def invert_by(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: None = None
+    ) -> "Chain[t.Dict[T2, t.List[T]]]": ...
+    @t.overload
+    def invert_by(
+        self: "Chain[t.Union[t.Iterator[T], t.Sequence[T]]]", iteratee: t.Callable[[T], T2]
+    ) -> "Chain[t.Dict[T2, t.List[int]]]": ...
+    @t.overload
+    def invert_by(
+        self: "Chain[t.Union[t.Iterator[T], t.Sequence[T]]]", iteratee: None = None
+    ) -> "Chain[t.Dict[T, t.List[int]]]": ...
+    def invert_by(self, iteratee=None):
+        return self._wrap(pyd.invert_by)(iteratee)
+
+    def invoke(self: "Chain[t.Any]", path: PathT, *args: t.Any, **kwargs: t.Any) -> "Chain[t.Any]":
+        return self._wrap(pyd.invoke)(path, *args, **kwargs)
+
+    @t.overload
+    def keys(self: "Chain[t.Iterable[T]]") -> "Chain[t.List[T]]": ...
+    @t.overload
+    def keys(self: "Chain[t.Any]") -> "Chain[t.List[t.Any]]": ...
+    def keys(self):
+        return self._wrap(pyd.keys)()
+
+    @t.overload
+    def map_keys(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T, t.Dict[T, T2]], T3]
+    ) -> "Chain[t.Dict[T3, T2]]": ...
+    @t.overload
+    def map_keys(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], T3]
+    ) -> "Chain[t.Dict[T3, T2]]": ...
+    @t.overload
+    def map_keys(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T2], T3]
+    ) -> "Chain[t.Dict[T3, T2]]": ...
+    @t.overload
+    def map_keys(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], T2]
+    ) -> "Chain[t.Dict[T2, T]]": ...
+    @t.overload
+    def map_keys(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], T2]
+    ) -> "Chain[t.Dict[T2, T]]": ...
+    @t.overload
+    def map_keys(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], T2]
+    ) -> "Chain[t.Dict[T2, T]]": ...
+    @t.overload
+    def map_keys(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Union[IterateeObjT, None] = None
+    ) -> "Chain[t.Dict[t.Any, t.Any]]": ...
+    def map_keys(self, iteratee=None):
+        return self._wrap(pyd.map_keys)(iteratee)
+
+    @t.overload
+    def map_values(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T, t.Dict[T, T2]], T3]
+    ) -> "Chain[t.Dict[T, T3]]": ...
+    @t.overload
+    def map_values(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], T3]
+    ) -> "Chain[t.Dict[T, T3]]": ...
+    @t.overload
+    def map_values(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2], T3]
+    ) -> "Chain[t.Dict[T, T3]]": ...
+    @t.overload
+    def map_values(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int, t.List[T]], T2]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def map_values(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T, int], T2]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def map_values(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T], T2]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def map_values(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Union[IterateeObjT, None] = None
+    ) -> "Chain[t.Dict[t.Any, t.Any]]": ...
+    def map_values(self, iteratee=None):
+        return self._wrap(pyd.map_values)(iteratee)
+
+    def map_values_deep(
+        self: "Chain[t.Iterable[t.Any]]",
+        iteratee: t.Union[t.Callable[..., t.Any], None] = None,
+        property_path: t.Any = UNSET,
+    ) -> "Chain[t.Any]":
+        return self._wrap(pyd.map_values_deep)(iteratee, property_path)
+
+    def apply(self: "Chain[T]", func: t.Callable[[T], T2]) -> "Chain[T2]":
+        return self._wrap(pyd.apply)(func)
+
+    def apply_if(
+        self: "Chain[T]", func: t.Callable[[T], T2], predicate: t.Callable[[T], bool]
+    ) -> "Chain[t.Union[T, T2]]":
+        return self._wrap(pyd.apply_if)(func, predicate)
+
+    def apply_if_not_none(
+        self: "Chain[t.Optional[T]]", func: t.Callable[[T], T2]
+    ) -> "Chain[t.Optional[T2]]":
+        return self._wrap(pyd.apply_if_not_none)(func)
+
+    @t.overload
+    def apply_catch(
+        self: "Chain[T]",
+        func: t.Callable[[T], T2],
+        exceptions: t.Iterable[t.Type[Exception]],
+        default: T3,
+    ) -> "Chain[t.Union[T2, T3]]": ...
+    @t.overload
+    def apply_catch(
+        self: "Chain[T]",
+        func: t.Callable[[T], T2],
+        exceptions: t.Iterable[t.Type[Exception]],
+        default: Unset = UNSET,
+    ) -> "Chain[t.Union[T, T2]]": ...
+    def apply_catch(self, func, exceptions, default=UNSET):
+        return self._wrap(pyd.apply_catch)(func, exceptions, default)
+
+    @t.overload
+    def merge(
+        self: "Chain[t.Mapping[T, T2]]", *sources: t.Mapping[T3, T4]
+    ) -> "Chain[t.Dict[t.Union[T, T3], t.Union[T2, T4]]]": ...
+    @t.overload
+    def merge(
+        self: "Chain[t.Sequence[T]]", *sources: t.Sequence[T2]
+    ) -> "Chain[t.List[t.Union[T, T2]]]": ...
+    def merge(self, *sources):
+        return self._wrap(pyd.merge)(*sources)
+
+    def merge_with(self: "Chain[t.Any]", *sources: t.Any, **kwargs: t.Any) -> "Chain[t.Any]":
+        return self._wrap(pyd.merge_with)(*sources, **kwargs)
+
+    @t.overload
+    def omit(self: "Chain[t.Mapping[T, T2]]", *properties: PathT) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def omit(
+        self: "Chain[t.Union[t.Iterator[T], t.Sequence[T]]]", *properties: PathT
+    ) -> "Chain[t.Dict[int, T]]": ...
+    @t.overload
+    def omit(self: "Chain[t.Any]", *properties: PathT) -> "Chain[t.Dict[t.Any, t.Any]]": ...
+    def omit(self, *properties):
+        return self._wrap(pyd.omit)(*properties)
+
+    @t.overload
+    def omit_by(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], t.Any]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def omit_by(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2], t.Any]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def omit_by(self: "Chain[t.Dict[T, T2]]", iteratee: None = None) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def omit_by(
+        self: "Chain[t.Union[t.Iterator[T], t.Sequence[T]]]", iteratee: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.Dict[int, T]]": ...
+    @t.overload
+    def omit_by(
+        self: "Chain[t.Union[t.Iterator[T], t.Sequence[T]]]", iteratee: t.Callable[[T], t.Any]
+    ) -> "Chain[t.Dict[int, T]]": ...
+    @t.overload
+    def omit_by(self: "Chain[t.List[T]]", iteratee: None = None) -> "Chain[t.Dict[int, T]]": ...
+    @t.overload
+    def omit_by(
+        self: "Chain[t.Any]", iteratee: t.Union[t.Callable[..., t.Any], None] = None
+    ) -> "Chain[t.Dict[t.Any, t.Any]]": ...
+    def omit_by(self, iteratee=None):
+        return self._wrap(pyd.omit_by)(iteratee)
+
+    def parse_int(
+        self: "Chain[t.Any]", radix: t.Union[int, None] = None
+    ) -> "Chain[t.Union[int, None]]":
+        return self._wrap(pyd.parse_int)(radix)
+
+    @t.overload
+    def pick(self: "Chain[t.Mapping[T, T2]]", *properties: PathT) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def pick(
+        self: "Chain[t.Union[t.Tuple[T, ...], t.List[T]]]", *properties: PathT
+    ) -> "Chain[t.Dict[int, T]]": ...
+    @t.overload
+    def pick(self: "Chain[t.Any]", *properties: PathT) -> "Chain[t.Dict[t.Any, t.Any]]": ...
+    def pick(self, *properties):
+        return self._wrap(pyd.pick)(*properties)
+
+    @t.overload
+    def pick_by(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2], t.Any]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def pick_by(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T2, T], t.Any]
+    ) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def pick_by(self: "Chain[t.Dict[T, T2]]", iteratee: None = None) -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def pick_by(
+        self: "Chain[t.Union[t.Tuple[T, ...], t.List[T]]]", iteratee: t.Callable[[T, int], t.Any]
+    ) -> "Chain[t.Dict[int, T]]": ...
+    @t.overload
+    def pick_by(
+        self: "Chain[t.Union[t.Tuple[T, ...], t.List[T]]]", iteratee: t.Callable[[T], t.Any]
+    ) -> "Chain[t.Dict[int, T]]": ...
+    @t.overload
+    def pick_by(
+        self: "Chain[t.Union[t.Tuple[T, ...], t.List[T]]]", iteratee: None = None
+    ) -> "Chain[t.Dict[int, T]]": ...
+    @t.overload
+    def pick_by(
+        self: "Chain[t.Any]", iteratee: t.Union[t.Callable[..., t.Any], None] = None
+    ) -> "Chain[t.Dict[t.Any, t.Any]]": ...
+    def pick_by(self, iteratee=None):
+        return self._wrap(pyd.pick_by)(iteratee)
+
+    def rename_keys(
+        self: "Chain[t.Dict[T, T2]]", key_map: t.Dict[t.Any, T3]
+    ) -> "Chain[t.Dict[t.Union[T, T3], T2]]":
+        return self._wrap(pyd.rename_keys)(key_map)
+
+    def set_(self: "Chain[T]", path: PathT, value: t.Any) -> "Chain[T]":
+        return self._wrap(pyd.set_)(path, value)
+
+    set = set_
+
+    def set_with(
+        self: "Chain[T]",
+        path: PathT,
+        value: t.Any,
+        customizer: t.Union[t.Callable[..., t.Any], None] = None,
+    ) -> "Chain[T]":
+        return self._wrap(pyd.set_with)(path, value, customizer)
+
+    def to_boolean(
+        self: "Chain[t.Any]",
+        true_values: t.Tuple[str, ...] = ("true", "1"),
+        false_values: t.Tuple[str, ...] = ("false", "0"),
+    ) -> "Chain[t.Union[bool, None]]":
+        return self._wrap(pyd.to_boolean)(true_values, false_values)
+
+    @t.overload
+    def to_dict(self: "Chain[t.Mapping[T, T2]]") -> "Chain[t.Dict[T, T2]]": ...
+    @t.overload
+    def to_dict(
+        self: "Chain[t.Union[t.Iterator[T], t.Sequence[T]]]",
+    ) -> "Chain[t.Dict[int, T]]": ...
+    @t.overload
+    def to_dict(self: "Chain[t.Any]") -> "Chain[t.Dict[t.Any, t.Any]]": ...
+    def to_dict(self):
+        return self._wrap(pyd.to_dict)()
+
+    def to_integer(self: "Chain[t.Any]") -> "Chain[int]":
+        return self._wrap(pyd.to_integer)()
+
+    @t.overload
+    def to_list(
+        self: "Chain[t.Dict[t.Any, T]]", split_strings: bool = True
+    ) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def to_list(self: "Chain[t.Iterable[T]]", split_strings: bool = True) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def to_list(self: "Chain[T]", split_strings: bool = True) -> "Chain[t.List[T]]": ...
+    def to_list(self, split_strings=True):
+        return self._wrap(pyd.to_list)(split_strings)
+
+    def to_number(self: "Chain[t.Any]", precision: int = 0) -> "Chain[t.Union[float, None]]":
+        return self._wrap(pyd.to_number)(precision)
+
+    @t.overload
+    def to_pairs(self: "Chain[t.Mapping[T, T2]]") -> "Chain[t.List[t.Tuple[T, T2]]]": ...
+    @t.overload
+    def to_pairs(
+        self: "Chain[t.Union[t.Iterator[T], t.Sequence[T]]]",
+    ) -> "Chain[t.List[t.Tuple[int, T]]]": ...
+    @t.overload
+    def to_pairs(self: "Chain[t.Any]") -> "Chain[t.List[t.Any]]": ...
+    def to_pairs(self):
+        return self._wrap(pyd.to_pairs)()
+
+    @t.overload
+    def transform(
+        self: "Chain[t.Mapping[T, T2]]",
+        iteratee: t.Callable[[T3, T2, T, t.Dict[T, T2]], t.Any],
+        accumulator: T3,
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def transform(
+        self: "Chain[t.Mapping[T, T2]]", iteratee: t.Callable[[T3, T2, T], t.Any], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def transform(
+        self: "Chain[t.Mapping[t.Any, T2]]", iteratee: t.Callable[[T3, T2], t.Any], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def transform(
+        self: "Chain[t.Mapping[t.Any, t.Any]]", iteratee: t.Callable[[T3], t.Any], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def transform(
+        self: "Chain[t.Iterable[T]]",
+        iteratee: t.Callable[[T3, T, int, t.List[T]], t.Any],
+        accumulator: T3,
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def transform(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T3, T, int], t.Any], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def transform(
+        self: "Chain[t.Iterable[T]]", iteratee: t.Callable[[T3, T], t.Any], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def transform(
+        self: "Chain[t.Iterable[t.Any]]", iteratee: t.Callable[[T3], t.Any], accumulator: T3
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def transform(
+        self: "Chain[t.Any]", iteratee: t.Any = None, accumulator: t.Any = None
+    ) -> "Chain[t.Any]": ...
+    def transform(self, iteratee=None, accumulator=None):
+        return self._wrap(pyd.transform)(iteratee, accumulator)
+
+    @t.overload
+    def update(
+        self: "Chain[t.Dict[t.Any, T2]]", path: PathT, updater: t.Callable[[T2], t.Any]
+    ) -> "Chain[t.Dict[t.Any, t.Any]]": ...
+    @t.overload
+    def update(
+        self: "Chain[t.List[T]]", path: PathT, updater: t.Callable[[T], t.Any]
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def update(self: "Chain[T]", path: PathT, updater: t.Callable[..., t.Any]) -> "Chain[T]": ...
+    def update(self, path, updater):
+        return self._wrap(pyd.update)(path, updater)
+
+    @t.overload
+    def update_with(
+        self: "Chain[t.Dict[t.Any, T2]]",
+        path: PathT,
+        updater: t.Callable[[T2], t.Any],
+        customizer: t.Union[t.Callable[..., t.Any], None],
+    ) -> "Chain[t.Dict[t.Any, t.Any]]": ...
+    @t.overload
+    def update_with(
+        self: "Chain[t.List[T]]",
+        path: PathT,
+        updater: t.Callable[[T], t.Any],
+        customizer: t.Union[t.Callable[..., t.Any], None] = None,
+    ) -> "Chain[t.List[t.Any]]": ...
+    @t.overload
+    def update_with(
+        self: "Chain[T]",
+        path: PathT,
+        updater: t.Callable[..., t.Any],
+        customizer: t.Union[t.Callable[..., t.Any], None] = None,
+    ) -> "Chain[T]": ...
+    def update_with(self, path, updater, customizer=None):
+        return self._wrap(pyd.update_with)(path, updater, customizer)
+
+    def unset(
+        self: "Chain[t.Union[t.List[t.Any], t.Dict[t.Any, t.Any]]]", path: PathT
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.unset)(path)
+
+    @t.overload
+    def values(self: "Chain[t.Mapping[t.Any, T2]]") -> "Chain[t.List[T2]]": ...
+    @t.overload
+    def values(self: "Chain[t.Iterable[T]]") -> "Chain[t.List[T]]": ...
+    @t.overload
+    def values(self: "Chain[t.Any]") -> "Chain[t.List[t.Any]]": ...
+    def values(self):
+        return self._wrap(pyd.values)()
+
+    def eq(self: "Chain[t.Any]", other: t.Any) -> "Chain[bool]":
+        return self._wrap(pyd.eq)(other)
+
+    def eq_cmp(self: "Chain[T]") -> "Chain[t.Callable[[T], bool]]":
+        return self._wrap(pyd.eq_cmp)()
+
+    def gt(self: "Chain['SupportsDunderGT[T]']", other: T) -> "Chain[bool]":
+        return self._wrap(pyd.gt)(other)
+
+    def gt_cmp(self: "Chain[T]") -> "Chain[t.Callable[['SupportsDunderGT[T]'], bool]]":
+        return self._wrap(pyd.gt_cmp)()
+
+    def gte(self: "Chain['SupportsDunderGE[T]']", other: T) -> "Chain[bool]":
+        return self._wrap(pyd.gte)(other)
+
+    def gte_cmp(self: "Chain[T]") -> "Chain[t.Callable[['SupportsDunderGE[T]'], bool]]":
+        return self._wrap(pyd.gte_cmp)()
+
+    def lt(self: "Chain['SupportsDunderLT[T]']", other: T) -> "Chain[bool]":
+        return self._wrap(pyd.lt)(other)
+
+    def lt_cmp(self: "Chain[T]") -> "Chain[t.Callable[['SupportsDunderLT[T]'], bool]]":
+        return self._wrap(pyd.lt_cmp)()
+
+    def lte(self: "Chain['SupportsDunderLE[T]']", other: T) -> "Chain[bool]":
+        return self._wrap(pyd.lte)(other)
+
+    def lte_cmp(self: "Chain[T]") -> "Chain[t.Callable[['SupportsDunderLE[T]'], bool]]":
+        return self._wrap(pyd.lte_cmp)()
+
+    def in_range(self: "Chain[t.Any]", start: t.Any = 0, end: t.Any = None) -> "Chain[bool]":
+        return self._wrap(pyd.in_range)(start, end)
+
+    def in_range_cmp(self: "Chain[t.Any]", end: t.Any = None) -> "Chain[t.Callable[[t.Any], bool]]":
+        return self._wrap(pyd.in_range_cmp)(end)
+
+    def is_associative(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_associative)()
+
+    def is_blank(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_blank)()
+
+    def is_boolean(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_boolean)()
+
+    def is_builtin(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_builtin)()
+
+    def is_date(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_date)()
+
+    def is_decreasing(
+        self: "Chain[t.Union['SupportsRichComparison', t.List['SupportsRichComparison']]]",
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.is_decreasing)()
+
+    def is_dict(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_dict)()
+
+    def is_empty(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_empty)()
+
+    def is_equal(self: "Chain[t.Any]", other: t.Any) -> "Chain[bool]":
+        return self._wrap(pyd.is_equal)(other)
+
+    def is_equal_cmp(self: "Chain[T]") -> "Chain[t.Callable[[T], bool]]":
+        return self._wrap(pyd.is_equal_cmp)()
+
+    @t.overload
+    def is_equal_with(
+        self: "Chain[T]", other: T2, customizer: t.Callable[[T, T2], T3]
+    ) -> "Chain[T3]": ...
+    @t.overload
+    def is_equal_with(
+        self: "Chain[t.Any]", other: t.Any, customizer: t.Callable[..., t.Any]
+    ) -> "Chain[bool]": ...
+    @t.overload
+    def is_equal_with(self: "Chain[t.Any]", other: t.Any, customizer: None) -> "Chain[bool]": ...
+    def is_equal_with(self, other, customizer):
+        return self._wrap(pyd.is_equal_with)(other, customizer)
+
+    def is_equal_with_cmp(
+        self: "Chain[T]", customizer: t.Callable[[T, T], T3]
+    ) -> "Chain[t.Callable[[T], T3]]":
+        return self._wrap(pyd.is_equal_with_cmp)(customizer)
+
+    def is_error(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_error)()
+
+    def is_even(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_even)()
+
+    def is_float(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_float)()
+
+    def is_function(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_function)()
+
+    def is_increasing(
+        self: "Chain[t.Union['SupportsRichComparison', t.List['SupportsRichComparison']]]",
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.is_increasing)()
+
+    def is_indexed(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_indexed)()
+
+    def is_instance_of(
+        self: "Chain[t.Any]", types: t.Union[type, t.Tuple[type, ...]]
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.is_instance_of)(types)
+
+    def is_instance_of_cmp(
+        self: "Chain[t.Union[type, t.Tuple[type, ...]]]",
+    ) -> "Chain[t.Callable[[t.Any], bool]]":
+        return self._wrap(pyd.is_instance_of_cmp)()
+
+    def is_integer(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_integer)()
+
+    def is_iterable(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_iterable)()
+
+    def is_json(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_json)()
+
+    def is_list(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_list)()
+
+    def is_match(self: "Chain[t.Any]", source: t.Any) -> "Chain[bool]":
+        return self._wrap(pyd.is_match)(source)
+
+    def is_match_cmp(self: "Chain[t.Any]") -> "Chain[t.Callable[[t.Any], bool]]":
+        return self._wrap(pyd.is_match_cmp)()
+
+    def is_match_with(
+        self: "Chain[t.Any]",
+        source: t.Any,
+        customizer: t.Any = None,
+        _key: t.Any = UNSET,
+        _obj: t.Any = UNSET,
+        _source: t.Any = UNSET,
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.is_match_with)(source, customizer, _key, _obj, _source)
+
+    def is_match_with_cmp(
+        self: "Chain[t.Any]", customizer: t.Any = None
+    ) -> "Chain[t.Callable[[t.Any], bool]]":
+        return self._wrap(pyd.is_match_with_cmp)(customizer)
+
+    def is_monotone(
+        self: "Chain[t.Union[T, t.List[T]]]", op: t.Callable[[T, T], t.Any]
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.is_monotone)(op)
+
+    def is_monotone_cmp(
+        self: "Chain[t.Callable[[T, T], t.Any]]",
+    ) -> "Chain[t.Callable[[t.Union[T, t.List[T]]], bool]]":
+        return self._wrap(pyd.is_monotone_cmp)()
+
+    def is_nan(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_nan)()
+
+    def is_negative(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_negative)()
+
+    def is_none(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_none)()
+
+    def is_number(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_number)()
+
+    def is_object(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_object)()
+
+    def is_odd(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_odd)()
+
+    def is_positive(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_positive)()
+
+    def is_reg_exp(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_reg_exp)()
+
+    def is_set(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_set)()
+
+    def is_strictly_decreasing(
+        self: "Chain[t.Union['SupportsRichComparison', t.List['SupportsRichComparison']]]",
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.is_strictly_decreasing)()
+
+    def is_strictly_increasing(
+        self: "Chain[t.Union['SupportsRichComparison', t.List['SupportsRichComparison']]]",
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.is_strictly_increasing)()
+
+    def is_string(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_string)()
+
+    def is_tuple(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_tuple)()
+
+    def is_zero(self: "Chain[t.Any]") -> "Chain[bool]":
+        return self._wrap(pyd.is_zero)()
+
+    def camel_case(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.camel_case)()
+
+    def capitalize(self: "Chain[t.Any]", strict: bool = True) -> "Chain[str]":
+        return self._wrap(pyd.capitalize)(strict)
+
+    def chars(self: "Chain[t.Any]") -> "Chain[t.List[str]]":
+        return self._wrap(pyd.chars)()
+
+    def chop(self: "Chain[t.Any]", step: int) -> "Chain[t.List[str]]":
+        return self._wrap(pyd.chop)(step)
+
+    def chop_right(self: "Chain[t.Any]", step: int) -> "Chain[t.List[str]]":
+        return self._wrap(pyd.chop_right)(step)
+
+    def clean(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.clean)()
+
+    def count_substr(self: "Chain[t.Any]", subtext: t.Any) -> "Chain[int]":
+        return self._wrap(pyd.count_substr)(subtext)
+
+    def deburr(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.deburr)()
+
+    def decapitalize(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.decapitalize)()
+
+    def ends_with(
+        self: "Chain[t.Any]", target: t.Any, position: t.Union[int, None] = None
+    ) -> "Chain[bool]":
+        return self._wrap(pyd.ends_with)(target, position)
+
+    def ensure_ends_with(self: "Chain[t.Any]", suffix: t.Any) -> "Chain[str]":
+        return self._wrap(pyd.ensure_ends_with)(suffix)
+
+    def ensure_starts_with(self: "Chain[t.Any]", prefix: t.Any) -> "Chain[str]":
+        return self._wrap(pyd.ensure_starts_with)(prefix)
+
+    def escape(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.escape)()
+
+    def escape_reg_exp(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.escape_reg_exp)()
+
+    def has_substr(self: "Chain[t.Any]", subtext: t.Any) -> "Chain[bool]":
+        return self._wrap(pyd.has_substr)(subtext)
+
+    def human_case(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.human_case)()
+
+    def insert_substr(self: "Chain[t.Any]", index: int, subtext: t.Any) -> "Chain[str]":
+        return self._wrap(pyd.insert_substr)(index, subtext)
+
+    def join(self: "Chain[t.Iterable[t.Any]]", separator: t.Any = "") -> "Chain[str]":
+        return self._wrap(pyd.join)(separator)
+
+    def kebab_case(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.kebab_case)()
+
+    def lines(self: "Chain[t.Any]") -> "Chain[t.List[str]]":
+        return self._wrap(pyd.lines)()
+
+    def lower_case(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.lower_case)()
+
+    def lower_first(self: "Chain[str]") -> "Chain[str]":
+        return self._wrap(pyd.lower_first)()
+
+    def number_format(
+        self: "Chain[NumberT]",
+        scale: int = 0,
+        decimal_separator: str = ".",
+        order_separator: str = ",",
+    ) -> "Chain[str]":
+        return self._wrap(pyd.number_format)(scale, decimal_separator, order_separator)
+
+    def pad(self: "Chain[t.Any]", length: int, chars: t.Any = " ") -> "Chain[str]":
+        return self._wrap(pyd.pad)(length, chars)
+
+    def pad_end(self: "Chain[t.Any]", length: int, chars: t.Any = " ") -> "Chain[str]":
+        return self._wrap(pyd.pad_end)(length, chars)
+
+    def pad_start(self: "Chain[t.Any]", length: int, chars: t.Any = " ") -> "Chain[str]":
+        return self._wrap(pyd.pad_start)(length, chars)
+
+    def pascal_case(self: "Chain[t.Any]", strict: bool = True) -> "Chain[str]":
+        return self._wrap(pyd.pascal_case)(strict)
+
+    def predecessor(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.predecessor)()
+
+    def prune(self: "Chain[t.Any]", length: int = 0, omission: str = "...") -> "Chain[str]":
+        return self._wrap(pyd.prune)(length, omission)
+
+    def quote(self: "Chain[t.Any]", quote_char: t.Any = '"') -> "Chain[str]":
+        return self._wrap(pyd.quote)(quote_char)
+
+    def reg_exp_js_match(self: "Chain[t.Any]", reg_exp: str) -> "Chain[t.List[str]]":
+        return self._wrap(pyd.reg_exp_js_match)(reg_exp)
+
+    def reg_exp_js_replace(
+        self: "Chain[t.Any]", reg_exp: str, repl: t.Union[str, t.Callable[[re.Match[str]], str]]
+    ) -> "Chain[str]":
+        return self._wrap(pyd.reg_exp_js_replace)(reg_exp, repl)
+
+    def reg_exp_replace(
+        self: "Chain[t.Any]",
+        pattern: t.Any,
+        repl: t.Union[str, t.Callable[[re.Match[str]], str]],
+        ignore_case: bool = False,
+        count: int = 0,
+    ) -> "Chain[str]":
+        return self._wrap(pyd.reg_exp_replace)(pattern, repl, ignore_case, count)
+
+    def repeat(self: "Chain[t.Any]", n: t.SupportsInt = 0) -> "Chain[str]":
+        return self._wrap(pyd.repeat)(n)
+
+    def replace(
+        self: "Chain[t.Any]",
+        pattern: t.Any,
+        repl: t.Union[str, t.Callable[[re.Match[str]], str]],
+        ignore_case: bool = False,
+        count: int = 0,
+        escape: bool = True,
+        from_start: bool = False,
+        from_end: bool = False,
+    ) -> "Chain[str]":
+        return self._wrap(pyd.replace)(
+            pattern, repl, ignore_case, count, escape, from_start, from_end
+        )
+
+    def replace_end(
+        self: "Chain[t.Any]",
+        pattern: t.Any,
+        repl: t.Union[str, t.Callable[[re.Match[str]], str]],
+        ignore_case: bool = False,
+        escape: bool = True,
+    ) -> "Chain[str]":
+        return self._wrap(pyd.replace_end)(pattern, repl, ignore_case, escape)
+
+    def replace_start(
+        self: "Chain[t.Any]",
+        pattern: t.Any,
+        repl: t.Union[str, t.Callable[[re.Match[str]], str]],
+        ignore_case: bool = False,
+        escape: bool = True,
+    ) -> "Chain[str]":
+        return self._wrap(pyd.replace_start)(pattern, repl, ignore_case, escape)
+
+    def separator_case(self: "Chain[t.Any]", separator: str) -> "Chain[str]":
+        return self._wrap(pyd.separator_case)(separator)
+
+    def series_phrase(
+        self: "Chain[t.List[t.Any]]",
+        separator: t.Any = ", ",
+        last_separator: t.Any = " and ",
+        serial: bool = False,
+    ) -> "Chain[str]":
+        return self._wrap(pyd.series_phrase)(separator, last_separator, serial)
+
+    def series_phrase_serial(
+        self: "Chain[t.List[t.Any]]", separator: t.Any = ", ", last_separator: t.Any = " and "
+    ) -> "Chain[str]":
+        return self._wrap(pyd.series_phrase_serial)(separator, last_separator)
+
+    def slugify(self: "Chain[t.Any]", separator: str = "-") -> "Chain[str]":
+        return self._wrap(pyd.slugify)(separator)
+
+    def snake_case(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.snake_case)()
+
+    def split(
+        self: "Chain[t.Any]", separator: t.Union[str, Unset, None] = UNSET
+    ) -> "Chain[t.List[str]]":
+        return self._wrap(pyd.split)(separator)
+
+    def start_case(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.start_case)()
+
+    def starts_with(self: "Chain[t.Any]", target: t.Any, position: int = 0) -> "Chain[bool]":
+        return self._wrap(pyd.starts_with)(target, position)
+
+    def strip_tags(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.strip_tags)()
+
+    def substr_left(self: "Chain[t.Any]", subtext: str) -> "Chain[str]":
+        return self._wrap(pyd.substr_left)(subtext)
+
+    def substr_left_end(self: "Chain[t.Any]", subtext: str) -> "Chain[str]":
+        return self._wrap(pyd.substr_left_end)(subtext)
+
+    def substr_right(self: "Chain[t.Any]", subtext: str) -> "Chain[str]":
+        return self._wrap(pyd.substr_right)(subtext)
+
+    def substr_right_end(self: "Chain[t.Any]", subtext: str) -> "Chain[str]":
+        return self._wrap(pyd.substr_right_end)(subtext)
+
+    def successor(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.successor)()
+
+    def surround(self: "Chain[t.Any]", wrapper: t.Any) -> "Chain[str]":
+        return self._wrap(pyd.surround)(wrapper)
+
+    def swap_case(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.swap_case)()
+
+    def title_case(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.title_case)()
+
+    def to_lower(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.to_lower)()
+
+    def to_upper(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.to_upper)()
+
+    def trim(self: "Chain[t.Any]", chars: t.Union[str, None] = None) -> "Chain[str]":
+        return self._wrap(pyd.trim)(chars)
+
+    def trim_end(self: "Chain[t.Any]", chars: t.Union[str, None] = None) -> "Chain[str]":
+        return self._wrap(pyd.trim_end)(chars)
+
+    def trim_start(self: "Chain[t.Any]", chars: t.Union[str, None] = None) -> "Chain[str]":
+        return self._wrap(pyd.trim_start)(chars)
+
+    def truncate(
+        self: "Chain[t.Any]",
+        length: int = 30,
+        omission: str = "...",
+        separator: t.Union[str, re.Pattern[str], None] = None,
+    ) -> "Chain[str]":
+        return self._wrap(pyd.truncate)(length, omission, separator)
+
+    def unescape(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.unescape)()
+
+    def upper_case(self: "Chain[t.Any]") -> "Chain[str]":
+        return self._wrap(pyd.upper_case)()
+
+    def upper_first(self: "Chain[str]") -> "Chain[str]":
+        return self._wrap(pyd.upper_first)()
+
+    def unquote(self: "Chain[t.Any]", quote_char: t.Any = '"') -> "Chain[str]":
+        return self._wrap(pyd.unquote)(quote_char)
+
+    def url(self: "Chain[t.Any]", *paths: t.Any, **params: t.Any) -> "Chain[str]":
+        return self._wrap(pyd.url)(*paths, **params)
+
+    def words(self: "Chain[t.Any]", pattern: t.Union[str, None] = None) -> "Chain[t.List[str]]":
+        return self._wrap(pyd.words)(pattern)
+
+    def attempt(
+        self: "Chain[t.Callable[P, T]]", *args: "P.args", **kwargs: "P.kwargs"
+    ) -> "Chain[t.Union[T, Exception]]":
+        return self._wrap(pyd.attempt)(*args, **kwargs)
+
+    @t.overload
+    def cond(
+        self: "Chain[t.List[t.Tuple[t.Callable[P, t.Any], t.Callable[P, T]]]]",
+        *extra_pairs: t.Tuple[t.Callable[P, t.Any], t.Callable[P, T]],
+    ) -> "Chain[t.Callable[P, T]]": ...
+    @t.overload
+    def cond(
+        self: "Chain[t.List[t.List[t.Callable[P, t.Any]]]]",
+        *extra_pairs: t.List[t.Callable[P, t.Any]],
+    ) -> "Chain[t.Callable[P, t.Any]]": ...
+    def cond(self, *extra_pairs):
+        return self._wrap(pyd.cond)(*extra_pairs)
+
+    @t.overload
+    def conforms(
+        self: "Chain[t.Dict[T, t.Callable[[T2], t.Any]]]",
+    ) -> "Chain[t.Callable[[t.Dict[T, T2]], bool]]": ...
+    @t.overload
+    def conforms(
+        self: "Chain[t.List[t.Callable[[T], t.Any]]]",
+    ) -> "Chain[t.Callable[[t.List[T]], bool]]": ...
+    def conforms(
+        self: "Chain[t.Union[t.List[t.Any], t.Dict[t.Any, t.Any]]]",
+    ) -> "Chain[t.Callable[..., t.Any]]":
+        return self._wrap(pyd.conforms)()
+
+    @t.overload
+    def conforms_to(
+        self: "Chain[t.Dict[T, T2]]", source: t.Dict[T, t.Callable[[T2], t.Any]]
+    ) -> "Chain[bool]": ...
+    @t.overload
+    def conforms_to(
+        self: "Chain[t.List[T]]", source: t.List[t.Callable[[T], t.Any]]
+    ) -> "Chain[bool]": ...
+    def conforms_to(self, source):
+        return self._wrap(pyd.conforms_to)(source)
+
+    def constant(self: "Chain[T]") -> "Chain[t.Callable[..., T]]":
+        return self._wrap(pyd.constant)()
+
+    def default_to(self: "Chain[t.Union[T, None]]", default_value: T2) -> "Chain[t.Union[T, T2]]":
+        return self._wrap(pyd.default_to)(default_value)
+
+    @t.overload
+    def default_to_any(self: "Chain[None]", *default_values: None) -> "Chain[None]": ...
+    @t.overload
+    def default_to_any(
+        self: "Chain[t.Union[T, None]]", default_value1: None, default_value2: T2
+    ) -> "Chain[t.Union[T, T2]]": ...
+    @t.overload
+    def default_to_any(
+        self: "Chain[t.Union[T, None]]",
+        default_value1: None,
+        default_value2: None,
+        default_value3: T2,
+    ) -> "Chain[t.Union[T, T2]]": ...
+    @t.overload
+    def default_to_any(
+        self: "Chain[t.Union[T, None]]",
+        default_value1: None,
+        default_value2: None,
+        default_value3: None,
+        default_value4: T2,
+    ) -> "Chain[t.Union[T, T2]]": ...
+    @t.overload
+    def default_to_any(
+        self: "Chain[t.Union[T, None]]",
+        default_value1: None,
+        default_value2: None,
+        default_value3: None,
+        default_value4: None,
+        default_value5: T2,
+    ) -> "Chain[t.Union[T, T2]]": ...
+    @t.overload
+    def default_to_any(
+        self: "Chain[t.Union[T, None]]", *default_values: T2
+    ) -> "Chain[t.Union[T, T2]]": ...
+    def default_to_any(self, *default_values):
+        return self._wrap(pyd.default_to_any)(*default_values)
+
+    @t.overload
+    def identity(self: "Chain[T]", *args: t.Any) -> "Chain[T]": ...
+    @t.overload
+    def iteratee(self: "Chain[t.Callable[P, T]]") -> "Chain[t.Callable[P, T]]": ...
+    @t.overload
+    def iteratee(self: "Chain[t.Any]") -> "Chain[t.Callable[..., t.Any]]": ...
+    def iteratee(self):
+        return self._wrap(pyd.iteratee)()
+
+    def matches(self: "Chain[t.Any]") -> "Chain[t.Callable[[t.Any], bool]]":
+        return self._wrap(pyd.matches)()
+
+    def matches_property(self: "Chain[t.Any]", value: t.Any) -> "Chain[t.Callable[[t.Any], bool]]":
+        return self._wrap(pyd.matches_property)(value)
+
+    @t.overload
+    def memoize(
+        self: "Chain[t.Callable[P, T]]", resolver: None = None
+    ) -> "Chain[MemoizedFunc[P, T, str]]": ...
+    @t.overload
+    def memoize(
+        self: "Chain[t.Callable[P, T]]", resolver: t.Union[t.Callable[P, T2], None] = None
+    ) -> "Chain[MemoizedFunc[P, T, T2]]": ...
+    def memoize(self, resolver=None):
+        return self._wrap(pyd.memoize)(resolver)
+
+    def method(
+        self: "Chain[PathT]", *args: t.Any, **kwargs: t.Any
+    ) -> "Chain[t.Callable[..., t.Any]]":
+        return self._wrap(pyd.method)(*args, **kwargs)
+
+    def method_of(
+        self: "Chain[t.Any]", *args: t.Any, **kwargs: t.Any
+    ) -> "Chain[t.Callable[..., t.Any]]":
+        return self._wrap(pyd.method_of)(*args, **kwargs)
+
+    def noop(self: "Chain[t.Any]", *args: t.Any, **kwargs: t.Any) -> "Chain[None]":
+        return self._wrap(pyd.noop)(*args, **kwargs)
+
+    def over(self: "Chain[t.Iterable[t.Callable[P, T]]]") -> "Chain[t.Callable[P, t.List[T]]]":
+        return self._wrap(pyd.over)()
+
+    def over_every(self: "Chain[t.Iterable[t.Callable[P, t.Any]]]") -> "Chain[t.Callable[P, bool]]":
+        return self._wrap(pyd.over_every)()
+
+    def over_some(self: "Chain[t.Iterable[t.Callable[P, t.Any]]]") -> "Chain[t.Callable[P, bool]]":
+        return self._wrap(pyd.over_some)()
+
+    def property_(self: "Chain[PathT]") -> "Chain[t.Callable[[t.Any], t.Any]]":
+        return self._wrap(pyd.property_)()
+
+    property = property_
+
+    def properties(self: "Chain[t.Any]", *paths: t.Any) -> "Chain[t.Callable[[t.Any], t.Any]]":
+        return self._wrap(pyd.properties)(*paths)
+
+    def property_of(self: "Chain[t.Any]") -> "Chain[t.Callable[[PathT], t.Any]]":
+        return self._wrap(pyd.property_of)()
+
+    @t.overload
+    def random(
+        self: "Chain[int]", stop: int = 1, *, floating: Literal[False] = False
+    ) -> "Chain[int]": ...
+    @t.overload
+    def random(self: "Chain[float]", stop: int = 1, floating: bool = False) -> "Chain[float]": ...
+    @t.overload
+    def random(self: "Chain[float]", stop: float, floating: bool = False) -> "Chain[float]": ...
+    @t.overload
+    def random(
+        self: "Chain[t.Union[float, int]]",
+        stop: t.Union[float, int] = 1,
+        *,
+        floating: Literal[True],
+    ) -> "Chain[float]": ...
+    def random(
+        self: "Chain[t.Union[float, int]]", stop: t.Union[float, int] = 1, floating: bool = False
+    ):
+        return self._wrap(pyd.random)(stop, floating)
+
+    @t.overload
+    def range_(self: "Chain[int]") -> "Chain[t.Generator[int, None, None]]": ...
+    @t.overload
+    def range_(
+        self: "Chain[int]", stop: int, step: int = 1
+    ) -> "Chain[t.Generator[int, None, None]]": ...
+    def range_(self, *args):
+        return self._wrap(pyd.range_)(*args)
+
+    range = range_
+
+    @t.overload
+    def range_right(self: "Chain[int]") -> "Chain[t.Generator[int, None, None]]": ...
+    @t.overload
+    def range_right(
+        self: "Chain[int]", stop: int, step: int = 1
+    ) -> "Chain[t.Generator[int, None, None]]": ...
+    def range_right(self, *args):
+        return self._wrap(pyd.range_right)(*args)
+
+    @t.overload
+    def result(self: "Chain[None]", key: t.Any, default: None = None) -> "Chain[None]": ...
+    @t.overload
+    def result(self: "Chain[None]", key: t.Any, default: T) -> "Chain[T]": ...
+    @t.overload
+    def result(self: "Chain[t.Any]", key: t.Any, default: t.Any = None) -> "Chain[t.Any]": ...
+    def result(self, key, default=None):
+        return self._wrap(pyd.result)(key, default)
+
+    def retry(
+        self: "Chain[int]",
+        delay: t.Union[int, float] = 0.5,
+        max_delay: t.Union[int, float] = 150.0,
+        scale: t.Union[int, float] = 2.0,
+        jitter: t.Union[int, float, t.Tuple[t.Union[int, float], t.Union[int, float]]] = 0,
+        exceptions: t.Iterable[Type[Exception]] = (Exception,),
+        on_exception: t.Union[t.Callable[[Exception, int], t.Any], None] = None,
+    ) -> "Chain[t.Callable[[CallableT], CallableT]]":
+        return self._wrap(pyd.retry)(delay, max_delay, scale, jitter, exceptions, on_exception)
+
+    @t.overload
+    def times(self: "Chain[int]", iteratee: t.Callable[..., T]) -> "Chain[t.List[T]]": ...
+    @t.overload
+    def times(self: "Chain[int]", iteratee: None = None) -> "Chain[t.List[int]]": ...
+    def times(self: "Chain[int]", iteratee=None):
+        return self._wrap(pyd.times)(iteratee)
+
+    def to_path(self: "Chain[PathT]") -> "Chain[t.List[t.Hashable]]":
+        return self._wrap(pyd.to_path)()
diff --git a/.venv/lib/python3.12/site-packages/pydash/chaining/chaining.py b/.venv/lib/python3.12/site-packages/pydash/chaining/chaining.py
new file mode 100644
index 00000000..09a364c5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/chaining/chaining.py
@@ -0,0 +1,264 @@
+"""
+Method chaining interface.
+
+.. versionadded:: 1.0.0
+"""
+
+import typing as t
+
+import pydash as pyd
+from pydash.exceptions import InvalidMethod
+
+from ..helpers import UNSET, Unset
+from .all_funcs import AllFuncs
+
+
+__all__ = (
+    "chain",
+    "tap",
+)
+
+ValueT_co = t.TypeVar("ValueT_co", covariant=True)
+T = t.TypeVar("T")
+T2 = t.TypeVar("T2")
+
+
+class Chain(AllFuncs, t.Generic[ValueT_co]):
+    """Enables chaining of :attr:`module` functions."""
+
+    #: Object that contains attribute references to available methods.
+    module = pyd
+    invalid_method_exception = InvalidMethod
+
+    def __init__(self, value: t.Union[ValueT_co, Unset] = UNSET) -> None:
+        self._value = value
+
+    def _wrap(self, func) -> "ChainWrapper[t.Union[ValueT_co, Unset]]":
+        """Implement `AllFuncs` interface."""
+        return ChainWrapper(self._value, func)
+
+    def value(self) -> ValueT_co:
+        """
+        Return current value of the chain operations.
+
+        Returns:
+            Current value of chain operations.
+        """
+        return self(self._value)
+
+    def to_string(self) -> str:
+        """
+        Return current value as string.
+
+        Returns:
+            Current value of chain operations casted to ``str``.
+        """
+        return self.module.to_string(self.value())
+
+    def commit(self) -> "Chain[ValueT_co]":
+        """
+        Executes the chained sequence and returns the wrapped result.
+
+        Returns:
+            New instance of :class:`Chain` with resolved value from
+                previous :class:`Class`.
+        """
+        return Chain(self.value())
+
+    def plant(self, value: t.Any) -> "Chain[ValueT_co]":
+        """
+        Return a clone of the chained sequence planting `value` as the wrapped value.
+
+        Args:
+            value: Value to plant as the initial chain value.
+        """
+        # pylint: disable=no-member,maybe-no-member
+        wrapper = self._value
+        wrappers = []
+
+        if hasattr(wrapper, "_value"):
+            wrappers = [wrapper]
+
+            while isinstance(wrapper._value, ChainWrapper):
+                wrapper = wrapper._value  # type: ignore
+                wrappers.insert(0, wrapper)
+
+        clone: Chain[t.Any] = Chain(value)
+
+        for wrap in wrappers:
+            clone = ChainWrapper(clone._value, wrap.method)(  # type: ignore
+                *wrap.args,  # type: ignore
+                **wrap.kwargs,  # type: ignore
+            )
+
+        return clone
+
+    def __call__(self, value) -> ValueT_co:
+        """
+        Return result of passing `value` through chained methods.
+
+        Args:
+            value: Initial value to pass through chained methods.
+
+        Returns:
+            Result of method chain evaluation of `value`.
+        """
+        if isinstance(self._value, ChainWrapper):
+            # pylint: disable=maybe-no-member
+            value = self._value.unwrap(value)
+        return value
+
+
+class ChainWrapper(t.Generic[ValueT_co]):
+    """Wrap :class:`Chain` method call within a :class:`ChainWrapper` context."""
+
+    def __init__(self, value: ValueT_co, method) -> None:
+        self._value = value
+        self.method = method
+        self.args = ()
+        self.kwargs: t.Dict[t.Any, t.Any] = {}
+
+    def _generate(self):
+        """Generate a copy of this instance."""
+        # pylint: disable=attribute-defined-outside-init
+        new = self.__class__.__new__(self.__class__)
+        new.__dict__ = self.__dict__.copy()
+        return new
+
+    def unwrap(self, value=UNSET):
+        """
+        Execute :meth:`method` with :attr:`_value`, :attr:`args`, and :attr:`kwargs`.
+
+        If :attr:`_value` is an instance of :class:`ChainWrapper`, then unwrap it before calling
+        :attr:`method`.
+        """
+        # Generate a copy of ourself so that we don't modify the chain wrapper
+        # _value directly. This way if we are late passing a value, we don't
+        # "freeze" the chain wrapper value when a value is first passed.
+        # Otherwise, we'd locked the chain wrapper value permanently and not be
+        # able to reuse it.
+        wrapper = self._generate()
+
+        if isinstance(wrapper._value, ChainWrapper):
+            # pylint: disable=no-member,maybe-no-member
+            wrapper._value = wrapper._value.unwrap(value)
+        elif not isinstance(value, ChainWrapper) and value is not UNSET:
+            # Override wrapper's initial value.
+            wrapper._value = value
+
+        if wrapper._value is not UNSET:
+            value = wrapper._value
+
+        return wrapper.method(value, *wrapper.args, **wrapper.kwargs)
+
+    def __call__(self, *args, **kwargs):
+        """
+        Invoke the :attr:`method` with :attr:`value` as the first argument and return a new
+        :class:`Chain` object with the return value.
+
+        Returns:
+            New instance of :class:`Chain` with the results of :attr:`method` passed in as
+                value.
+        """
+        self.args = args
+        self.kwargs = kwargs
+        return Chain(self)
+
+
+class _Dash(object):
+    """Class that provides attribute access to valid :mod:`pydash` methods and callable access to
+    :mod:`pydash` method chaining."""
+
+    def __getattr__(self, attr):
+        """Proxy to :meth:`Chain.get_method`."""
+        return Chain.get_method(attr)
+
+    def __call__(self, value: t.Union[ValueT_co, Unset] = UNSET) -> Chain[ValueT_co]:
+        """Return a new instance of :class:`Chain` with `value` as the seed."""
+        return Chain(value)
+
+
+def chain(value: t.Union[T, Unset] = UNSET) -> Chain[T]:
+    """
+    Creates a :class:`Chain` object which wraps the given value to enable intuitive method chaining.
+    Chaining is lazy and won't compute a final value until :meth:`Chain.value` is called.
+
+    Args:
+        value: Value to initialize chain operations with.
+
+    Returns:
+        Instance of :class:`Chain` initialized with `value`.
+
+    Example:
+
+        >>> chain([1, 2, 3, 4]).map(lambda x: x * 2).sum().value()
+        20
+        >>> chain().map(lambda x: x * 2).sum()([1, 2, 3, 4])
+        20
+
+        >>> summer = chain([1, 2, 3, 4]).sum()
+        >>> new_summer = summer.plant([1, 2])
+        >>> new_summer.value()
+        3
+        >>> summer.value()
+        10
+
+        >>> def echo(item):
+        ...     print(item)
+        >>> summer = chain([1, 2, 3, 4]).for_each(echo).sum()
+        >>> committed = summer.commit()
+        1
+        2
+        3
+        4
+        >>> committed.value()
+        10
+        >>> summer.value()
+        1
+        2
+        3
+        4
+        10
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 2.0.0
+        Made chaining lazy.
+
+    .. versionchanged:: 3.0.0
+
+        - Added support for late passing of `value`.
+        - Added :meth:`Chain.plant` for replacing initial chain value.
+        - Added :meth:`Chain.commit` for returning a new :class:`Chain` instance initialized with
+          the results from calling :meth:`Chain.value`.
+    """
+    return Chain(value)
+
+
+def tap(value: T, interceptor: t.Callable[[T], t.Any]) -> T:
+    """
+    Invokes `interceptor` with the `value` as the first argument and then returns `value`. The
+    purpose of this method is to "tap into" a method chain in order to perform operations on
+    intermediate results within the chain.
+
+    Args:
+        value: Current value of chain operation.
+        interceptor: Function called on `value`.
+
+    Returns:
+        `value` after `interceptor` call.
+
+    Example:
+
+        >>> data = []
+        >>> def log(value):
+        ...     data.append(value)
+        >>> chain([1, 2, 3, 4]).map(lambda x: x * 2).tap(log).value()
+        [2, 4, 6, 8]
+        >>> data
+        [[2, 4, 6, 8]]
+
+    .. versionadded:: 1.0.0
+    """
+    interceptor(value)
+    return value
diff --git a/.venv/lib/python3.12/site-packages/pydash/collections.py b/.venv/lib/python3.12/site-packages/pydash/collections.py
new file mode 100644
index 00000000..22c498d8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/collections.py
@@ -0,0 +1,2181 @@
+"""
+Functions that operate on lists and dicts.
+
+.. versionadded:: 1.0.0
+"""
+
+from __future__ import annotations
+
+from functools import cmp_to_key
+import random
+import typing as t
+
+import pydash as pyd
+
+from .helpers import callit, cmp, getargcount, iterator, iteriteratee
+from .types import IterateeObjT, PathT
+
+
+__all__ = (
+    "at",
+    "count_by",
+    "every",
+    "filter_",
+    "find",
+    "find_last",
+    "flat_map",
+    "flat_map_deep",
+    "flat_map_depth",
+    "for_each",
+    "for_each_right",
+    "group_by",
+    "includes",
+    "invoke_map",
+    "key_by",
+    "map_",
+    "nest",
+    "order_by",
+    "partition",
+    "pluck",
+    "reduce_",
+    "reduce_right",
+    "reductions",
+    "reductions_right",
+    "reject",
+    "sample",
+    "sample_size",
+    "shuffle",
+    "size",
+    "some",
+    "sort_by",
+)
+
+T = t.TypeVar("T")
+T2 = t.TypeVar("T2")
+T3 = t.TypeVar("T3")
+T4 = t.TypeVar("T4")
+
+
+@t.overload
+def at(collection: t.Mapping[T, T2], *paths: T) -> t.List[t.Union[T2, None]]: ...
+
+
+@t.overload
+def at(collection: t.Mapping[T, t.Any], *paths: t.Union[T, t.Iterable[T]]) -> t.List[t.Any]: ...
+
+
+@t.overload
+def at(collection: t.Iterable[T], *paths: int) -> t.List[t.Union[T, None]]: ...
+
+
+@t.overload
+def at(collection: t.Iterable[t.Any], *paths: t.Union[int, t.Iterable[int]]) -> t.List[t.Any]: ...
+
+
+def at(collection, *paths):
+    """
+    Creates a list of elements from the specified indexes, or keys, of the collection. Indexes may
+    be specified as individual arguments or as arrays of indexes.
+
+    Args:
+        collection: Collection to iterate over.
+        *paths: The indexes of `collection` to retrieve, specified as individual indexes or
+            arrays of indexes.
+
+    Returns:
+        filtered list
+
+    Example:
+
+        >>> at([1, 2, 3, 4], 0, 2)
+        [1, 3]
+        >>> at({"a": 1, "b": 2, "c": 3, "d": 4}, "a", "c")
+        [1, 3]
+        >>> at({"a": 1, "b": 2, "c": {"d": {"e": 3}}}, "a", ["c", "d", "e"])
+        [1, 3]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.1.0
+        Support deep path access.
+    """
+    return pyd.properties(*paths)(collection)
+
+
+@t.overload
+def count_by(collection: t.Mapping[t.Any, T2], iteratee: None = None) -> t.Dict[T2, int]: ...
+
+
+@t.overload
+def count_by(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T, t.Dict[T, T2]], T3]
+) -> t.Dict[T3, int]: ...
+
+
+@t.overload
+def count_by(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], T3]
+) -> t.Dict[T3, int]: ...
+
+
+@t.overload
+def count_by(
+    collection: t.Mapping[t.Any, T2], iteratee: t.Callable[[T2], T3]
+) -> t.Dict[T3, int]: ...
+
+
+@t.overload
+def count_by(collection: t.Iterable[T], iteratee: None = None) -> t.Dict[T, int]: ...
+
+
+@t.overload
+def count_by(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], T2]
+) -> t.Dict[T2, int]: ...
+
+
+@t.overload
+def count_by(collection: t.Iterable[T], iteratee: t.Callable[[T, int], T2]) -> t.Dict[T2, int]: ...
+
+
+@t.overload
+def count_by(collection: t.Iterable[T], iteratee: t.Callable[[T], T2]) -> t.Dict[T2, int]: ...
+
+
+def count_by(collection, iteratee=None):
+    """
+    Creates an object composed of keys generated from the results of running each element of
+    `collection` through the iteratee.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Dict containing counts by key.
+
+    Example:
+
+        >>> results = count_by([1, 2, 1, 2, 3, 4])
+        >>> assert results == {1: 2, 2: 2, 3: 1, 4: 1}
+        >>> results = count_by(["a", "A", "B", "b"], lambda x: x.lower())
+        >>> assert results == {"a": 2, "b": 2}
+        >>> results = count_by({"a": 1, "b": 1, "c": 3, "d": 3})
+        >>> assert results == {1: 2, 3: 2}
+
+    .. versionadded:: 1.0.0
+    """
+    ret = {}
+
+    for result in iteriteratee(collection, iteratee):
+        ret.setdefault(result[0], 0)
+        ret[result[0]] += 1
+
+    return ret
+
+
+def every(
+    collection: t.Iterable[T], predicate: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None
+) -> bool:
+    """
+    Checks if the predicate returns a truthy value for all elements of a collection. The predicate
+    is invoked with three arguments: ``(value, index|key, collection)``. If a property name is
+    passed for predicate, the created :func:`pluck` style predicate will return the property value
+    of the given element. If an object is passed for predicate, the created :func:`.matches` style
+    predicate will return ``True`` for elements that have the properties of the given object, else
+    ``False``.
+
+    Args:
+        collection: Collection to iterate over.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        Whether all elements are truthy.
+
+    Example:
+
+        >>> every([1, True, "hello"])
+        True
+        >>> every([1, False, "hello"])
+        False
+        >>> every([{"a": 1}, {"a": True}, {"a": "hello"}], "a")
+        True
+        >>> every([{"a": 1}, {"a": False}, {"a": "hello"}], "a")
+        False
+        >>> every([{"a": 1}, {"a": 1}], {"a": 1})
+        True
+        >>> every([{"a": 1}, {"a": 2}], {"a": 1})
+        False
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged: 4.0.0
+        Removed alias ``all_``.
+    """
+    if predicate:
+        cbk = pyd.iteratee(predicate)
+        collection = (cbk(item) for item in collection)
+
+    return all(collection)
+
+
+@t.overload
+def filter_(
+    collection: t.Mapping[T, T2],
+    predicate: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT, None] = None,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def filter_(
+    collection: t.Mapping[T, T2],
+    predicate: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT, None] = None,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def filter_(
+    collection: t.Mapping[t.Any, T2],
+    predicate: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def filter_(
+    collection: t.Iterable[T],
+    predicate: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT, None] = None,
+) -> t.List[T]: ...
+
+
+@t.overload
+def filter_(
+    collection: t.Iterable[T],
+    predicate: t.Union[t.Callable[[T, int], t.Any], IterateeObjT, None] = None,
+) -> t.List[T]: ...
+
+
+@t.overload
+def filter_(
+    collection: t.Iterable[T],
+    predicate: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+) -> t.List[T]: ...
+
+
+def filter_(collection, predicate=None):
+    """
+    Iterates over elements of a collection, returning a list of all elements the predicate returns
+    truthy for.
+
+    Args:
+        collection: Collection to iterate over.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        Filtered list.
+
+    Example:
+
+        >>> results = filter_([{"a": 1}, {"b": 2}, {"a": 1, "b": 3}], {"a": 1})
+        >>> assert results == [{"a": 1}, {"a": 1, "b": 3}]
+        >>> filter_([1, 2, 3, 4], lambda x: x >= 3)
+        [3, 4]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``select``.
+    """
+    return [value for is_true, value, _, _ in iteriteratee(collection, predicate) if is_true]
+
+
+@t.overload
+def find(
+    collection: t.Dict[T, T2],
+    predicate: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T2, None]: ...
+
+
+@t.overload
+def find(
+    collection: t.Dict[T, T2],
+    predicate: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T2, None]: ...
+
+
+@t.overload
+def find(
+    collection: t.Dict[T, T2],
+    predicate: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T2, None]: ...
+
+
+@t.overload
+def find(
+    collection: t.List[T],
+    predicate: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find(
+    collection: t.List[T],
+    predicate: t.Union[t.Callable[[T, int], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find(
+    collection: t.List[T],
+    predicate: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T, None]: ...
+
+
+def find(collection, predicate=None):
+    """
+    Iterates over elements of a collection, returning the first element that the predicate returns
+    truthy for.
+
+    Args:
+        collection: Collection to iterate over.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        First element found or ``None``.
+
+    Example:
+
+        >>> find([1, 2, 3, 4], lambda x: x >= 3)
+        3
+        >>> find([{"a": 1}, {"b": 2}, {"a": 1, "b": 2}], {"a": 1})
+        {'a': 1}
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed aliases ``detect`` and ``find_where``.
+    """
+    search = (value for is_true, value, _, _ in iteriteratee(collection, predicate) if is_true)
+    return next(search, None)
+
+
+@t.overload
+def find_last(
+    collection: t.Dict[T, T2],
+    predicate: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T2, None]: ...
+
+
+@t.overload
+def find_last(
+    collection: t.Dict[T, T2],
+    predicate: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T2, None]: ...
+
+
+@t.overload
+def find_last(
+    collection: t.Dict[t.Any, T2],
+    predicate: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T2, None]: ...
+
+
+@t.overload
+def find_last(
+    collection: t.List[T],
+    predicate: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find_last(
+    collection: t.List[T],
+    predicate: t.Union[t.Callable[[T, int], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find_last(
+    collection: t.List[T],
+    predicate: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+) -> t.Union[T, None]: ...
+
+
+def find_last(collection, predicate=None):
+    """
+    This method is like :func:`find` except that it iterates over elements of a `collection` from
+    right to left.
+
+    Args:
+        collection: Collection to iterate over.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        Last element found or ``None``.
+
+    Example:
+
+        >>> find_last([1, 2, 3, 4], lambda x: x >= 3)
+        4
+        >>> results = find_last([{'a': 1}, {'b': 2}, {'a': 1, 'b': 2}],\
+                                 {'a': 1})
+        >>> assert results == {'a': 1, 'b': 2}
+
+    .. versionadded:: 1.0.0
+    """
+    search = (
+        value
+        for is_true, value, _, _ in iteriteratee(collection, predicate, reverse=True)
+        if is_true
+    )
+    return next(search, None)
+
+
+@t.overload
+def flat_map(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T, t.Dict[T, T2]], t.Iterable[T3]]
+) -> t.List[T3]: ...
+
+
+@t.overload
+def flat_map(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], t.Iterable[T3]]
+) -> t.List[T3]: ...
+
+
+@t.overload
+def flat_map(
+    collection: t.Mapping[t.Any, T2], iteratee: t.Callable[[T2], t.Iterable[T3]]
+) -> t.List[T3]: ...
+
+
+@t.overload
+def flat_map(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T, t.Dict[T, T2]], T3]
+) -> t.List[T3]: ...
+
+
+@t.overload
+def flat_map(collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], T3]) -> t.List[T3]: ...
+
+
+@t.overload
+def flat_map(collection: t.Mapping[t.Any, T2], iteratee: t.Callable[[T2], T3]) -> t.List[T3]: ...
+
+
+@t.overload
+def flat_map(collection: t.Mapping[t.Any, t.Iterable[T2]], iteratee: None = None) -> t.List[T2]: ...
+
+
+@t.overload
+def flat_map(collection: t.Mapping[t.Any, T2], iteratee: None = None) -> t.List[T2]: ...
+
+
+@t.overload
+def flat_map(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], t.Iterable[T2]]
+) -> t.List[T2]: ...
+
+
+@t.overload
+def flat_map(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int], t.Iterable[T2]]
+) -> t.List[T2]: ...
+
+
+@t.overload
+def flat_map(
+    collection: t.Iterable[T], iteratee: t.Callable[[T], t.Iterable[T2]]
+) -> t.List[T2]: ...
+
+
+@t.overload
+def flat_map(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], T2]
+) -> t.List[T2]: ...
+
+
+@t.overload
+def flat_map(collection: t.Iterable[T], iteratee: t.Callable[[T, int], T2]) -> t.List[T2]: ...
+
+
+@t.overload
+def flat_map(collection: t.Iterable[T], iteratee: t.Callable[[T], T2]) -> t.List[T2]: ...
+
+
+@t.overload
+def flat_map(collection: t.Iterable[t.Iterable[T]], iteratee: None = None) -> t.List[T]: ...
+
+
+@t.overload
+def flat_map(collection: t.Iterable[T], iteratee: None = None) -> t.List[T]: ...
+
+
+def flat_map(collection, iteratee=None):
+    """
+    Creates a flattened list of values by running each element in collection through `iteratee` and
+    flattening the mapped results. The `iteratee` is invoked with three arguments: ``(value,
+    index|key, collection)``.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Flattened mapped list.
+
+    Example:
+
+        >>> duplicate = lambda n: [[n, n]]
+        >>> flat_map([1, 2], duplicate)
+        [[1, 1], [2, 2]]
+
+    .. versionadded:: 4.0.0
+    """
+    return pyd.flatten(itermap(collection, iteratee=iteratee))
+
+
+@t.overload
+def flat_map_deep(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], None] = None,
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def flat_map_deep(
+    collection: t.Mapping[T, T2], iteratee: t.Union[t.Callable[[T2, T], t.Any], None] = None
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def flat_map_deep(
+    collection: t.Mapping[t.Any, T2], iteratee: t.Union[t.Callable[[T2], t.Any], None] = None
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def flat_map_deep(
+    collection: t.Iterable[T],
+    iteratee: t.Union[t.Callable[[T, int, t.List[T]], t.Any], None] = None,
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def flat_map_deep(
+    collection: t.Iterable[T], iteratee: t.Union[t.Callable[[T, int], t.Any], None] = None
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def flat_map_deep(
+    collection: t.Iterable[T], iteratee: t.Union[t.Callable[[T], t.Any], None] = None
+) -> t.List[t.Any]: ...
+
+
+def flat_map_deep(collection, iteratee=None):
+    """
+    This method is like :func:`flat_map` except that it recursively flattens the mapped results.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Flattened mapped list.
+
+    Example:
+
+        >>> duplicate = lambda n: [[n, n]]
+        >>> flat_map_deep([1, 2], duplicate)
+        [1, 1, 2, 2]
+
+    .. versionadded:: 4.0.0
+    """
+    return pyd.flatten_deep(itermap(collection, iteratee=iteratee))
+
+
+@t.overload
+def flat_map_depth(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], None] = None,
+    depth: int = 1,
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def flat_map_depth(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Union[t.Callable[[T2, T], t.Any], None] = None,
+    depth: int = 1,
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def flat_map_depth(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Union[t.Callable[[T2], t.Any], None] = None,
+    depth: int = 1,
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def flat_map_depth(
+    collection: t.Iterable[T],
+    iteratee: t.Union[t.Callable[[T, int, t.List[T]], t.Any], None] = None,
+    depth: int = 1,
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def flat_map_depth(
+    collection: t.Iterable[T],
+    iteratee: t.Union[t.Callable[[T, int], t.Any], None] = None,
+    depth: int = 1,
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def flat_map_depth(
+    collection: t.Iterable[T],
+    iteratee: t.Union[t.Callable[[T], t.Any], None] = None,
+    depth: int = 1,
+) -> t.List[t.Any]: ...
+
+
+def flat_map_depth(collection, iteratee=None, depth=1):
+    """
+    This method is like :func:`flat_map` except that it recursively flattens the mapped results up
+    to `depth` times.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Flattened mapped list.
+
+    Example:
+
+        >>> duplicate = lambda n: [[n, n]]
+        >>> flat_map_depth([1, 2], duplicate, 1)
+        [[1, 1], [2, 2]]
+        >>> flat_map_depth([1, 2], duplicate, 2)
+        [1, 1, 2, 2]
+
+    .. versionadded:: 4.0.0
+    """
+    return pyd.flatten_depth(itermap(collection, iteratee=iteratee), depth=depth)
+
+
+@t.overload
+def for_each(
+    collection: t.Dict[T, T2],
+    iteratee: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT, None] = None,
+) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_each(
+    collection: t.Dict[T, T2],
+    iteratee: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT, None] = None,
+) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_each(
+    collection: t.Dict[T, T2],
+    iteratee: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_each(
+    collection: t.List[T],
+    iteratee: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT, None] = None,
+) -> t.List[T]: ...
+
+
+@t.overload
+def for_each(
+    collection: t.List[T],
+    iteratee: t.Union[t.Callable[[T, int], t.Any], IterateeObjT, None] = None,
+) -> t.List[T]: ...
+
+
+@t.overload
+def for_each(
+    collection: t.List[T],
+    iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+) -> t.List[T]: ...
+
+
+def for_each(collection, iteratee=None):
+    """
+    Iterates over elements of a collection, executing the iteratee for each element.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        `collection`
+
+    Example:
+
+        >>> results = {}
+        >>> def cb(x):
+        ...     results[x] = x**2
+        >>> for_each([1, 2, 3, 4], cb)
+        [1, 2, 3, 4]
+        >>> assert results == {1: 1, 2: 4, 3: 9, 4: 16}
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``each``.
+    """
+    next((None for ret, _, _, _ in iteriteratee(collection, iteratee) if ret is False), None)
+    return collection
+
+
+@t.overload
+def for_each_right(
+    collection: t.Dict[T, T2],
+    iteratee: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT],
+) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_each_right(
+    collection: t.Dict[T, T2],
+    iteratee: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT],
+) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_each_right(
+    collection: t.Dict[T, T2],
+    iteratee: t.Union[t.Callable[[T2], t.Any], IterateeObjT],
+) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_each_right(
+    collection: t.List[T],
+    iteratee: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT],
+) -> t.List[T]: ...
+
+
+@t.overload
+def for_each_right(
+    collection: t.List[T],
+    iteratee: t.Union[t.Callable[[T, int], t.Any], IterateeObjT],
+) -> t.List[T]: ...
+
+
+@t.overload
+def for_each_right(
+    collection: t.List[T],
+    iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT],
+) -> t.List[T]: ...
+
+
+def for_each_right(collection, iteratee):
+    """
+    This method is like :func:`for_each` except that it iterates over elements of a `collection`
+    from right to left.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        `collection`
+
+    Example:
+
+        >>> results = {"total": 1}
+        >>> def cb(x):
+        ...     results["total"] = x * results["total"]
+        >>> for_each_right([1, 2, 3, 4], cb)
+        [1, 2, 3, 4]
+        >>> assert results == {"total": 24}
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``each_right``.
+    """
+    next(
+        (None for ret, _, _, _ in iteriteratee(collection, iteratee, reverse=True) if ret is False),
+        None,
+    )
+    return collection
+
+
+@t.overload
+def group_by(collection: t.Iterable[T], iteratee: t.Callable[[T], T2]) -> t.Dict[T2, t.List[T]]: ...
+
+
+@t.overload
+def group_by(
+    collection: t.Iterable[T], iteratee: t.Union[IterateeObjT, None] = None
+) -> t.Dict[t.Any, t.List[T]]: ...
+
+
+def group_by(collection, iteratee=None):
+    """
+    Creates an object composed of keys generated from the results of running each element of a
+    `collection` through the iteratee.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Results of grouping by `iteratee`.
+
+    Example:
+
+        >>> results = group_by([{'a': 1, 'b': 2}, {'a': 3, 'b': 4}], 'a')
+        >>> assert results == {1: [{'a': 1, 'b': 2}], 3: [{'a': 3, 'b': 4}]}
+        >>> results = group_by([{'a': 1, 'b': 2}, {'a': 3, 'b': 4}], {'a': 1})
+        >>> assert results == {False: [{'a': 3, 'b': 4}],\
+                               True: [{'a': 1, 'b': 2}]}
+
+    .. versionadded:: 1.0.0
+    """
+    ret = {}
+    cbk = pyd.iteratee(iteratee)
+
+    for value in collection:
+        key = cbk(value)
+        ret.setdefault(key, [])
+        ret[key].append(value)
+
+    return ret
+
+
+def includes(
+    collection: t.Union[t.Sequence[t.Any], t.Dict[t.Any, t.Any]], target: t.Any, from_index: int = 0
+) -> bool:
+    """
+    Checks if a given value is present in a collection. If `from_index` is negative, it is used as
+    the offset from the end of the collection.
+
+    Args:
+        collection: Collection to iterate over.
+        target: Target value to compare to.
+        from_index: Offset to start search from.
+
+    Returns:
+        Whether `target` is in `collection`.
+
+    Example:
+
+        >>> includes([1, 2, 3, 4], 2)
+        True
+        >>> includes([1, 2, 3, 4], 2, from_index=2)
+        False
+        >>> includes({"a": 1, "b": 2, "c": 3, "d": 4}, 2)
+        True
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``contains`` to ``includes`` and removed alias
+        ``include``.
+    """
+    collection_values: t.Container[t.Any]
+    if isinstance(collection, dict):
+        collection_values = collection.values()
+    else:
+        # only makes sense to do this if `collection` is not a dict
+        collection_values = collection[from_index:]
+
+    return target in collection_values
+
+
+def invoke_map(
+    collection: t.Iterable[t.Any], path: PathT, *args: t.Any, **kwargs: t.Any
+) -> t.List[t.Any]:
+    """
+    Invokes the method at `path` of each element in `collection`, returning a list of the results of
+    each invoked method. Any additional arguments are provided to each invoked method. If `path` is
+    a function, it's invoked for each element in `collection`.
+
+    Args:
+        collection: Collection to iterate over.
+        path: String path to method to invoke or callable to invoke for each element in
+            `collection`.
+        args: Arguments to pass to method call.
+        kwargs: Keyword arguments to pass to method call.
+
+    Returns:
+        List of results of invoking method of each item.
+
+    Example:
+
+        >>> items = [{"a": [{"b": 1}]}, {"a": [{"c": 2}]}]
+        >>> expected = [{"b": 1}.items(), {"c": 2}.items()]
+        >>> invoke_map(items, "a[0].items") == expected
+        True
+
+    .. versionadded:: 4.0.0
+    """
+    return map_(collection, lambda item: pyd.invoke(item, path, *args, **kwargs))
+
+
+@t.overload
+def key_by(collection: t.Iterable[T], iteratee: t.Callable[[T], T2]) -> t.Dict[T2, T]: ...
+
+
+@t.overload
+def key_by(
+    collection: t.Iterable[t.Any], iteratee: t.Union[IterateeObjT, None] = None
+) -> t.Dict[t.Any, t.Any]: ...
+
+
+def key_by(collection, iteratee=None):
+    """
+    Creates an object composed of keys generated from the results of running each element of the
+    collection through the given iteratee.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Results of indexing by `iteratee`.
+
+    Example:
+
+        >>> results = key_by([{"a": 1, "b": 2}, {"a": 3, "b": 4}], "a")
+        >>> assert results == {1: {"a": 1, "b": 2}, 3: {"a": 3, "b": 4}}
+
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``index_by`` to ``key_by``.
+    """
+    ret = {}
+    cbk = pyd.iteratee(iteratee)
+
+    for value in collection:
+        ret[cbk(value)] = value
+
+    return ret
+
+
+@t.overload
+def map_(collection: t.Mapping[t.Any, T2], iteratee: t.Callable[[T2], T3]) -> t.List[T3]: ...
+
+
+@t.overload
+def map_(collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], T3]) -> t.List[T3]: ...
+
+
+@t.overload
+def map_(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T, t.Dict[T, T2]], T3]
+) -> t.List[T3]: ...
+
+
+@t.overload
+def map_(collection: t.Iterable[T], iteratee: t.Callable[[T], T2]) -> t.List[T2]: ...
+
+
+@t.overload
+def map_(collection: t.Iterable[T], iteratee: t.Callable[[T, int], T2]) -> t.List[T2]: ...
+
+
+@t.overload
+def map_(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], T2]
+) -> t.List[T2]: ...
+
+
+@t.overload
+def map_(
+    collection: t.Iterable[t.Any], iteratee: t.Union[IterateeObjT, None] = None
+) -> t.List[t.Any]: ...
+
+
+def map_(collection, iteratee=None):
+    """
+    Creates an array of values by running each element in the collection through the iteratee. The
+    iteratee is invoked with three arguments: ``(value, index|key, collection)``. If a property name
+    is passed for iteratee, the created :func:`pluck` style iteratee will return the property value
+    of the given element. If an object is passed for iteratee, the created :func:`.matches` style
+    iteratee will return ``True`` for elements that have the properties of the given object, else
+    ``False``.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Mapped list.
+
+    Example:
+
+        >>> map_([1, 2, 3, 4], str)
+        ['1', '2', '3', '4']
+        >>> map_([{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6}], "a")
+        [1, 3, 5]
+        >>> map_([[[0, 1]], [[2, 3]], [[4, 5]]], "0.1")
+        [1, 3, 5]
+        >>> map_([{"a": {"b": 1}}, {"a": {"b": 2}}], "a.b")
+        [1, 2]
+        >>> map_([{"a": {"b": [0, 1]}}, {"a": {"b": [2, 3]}}], "a.b[1]")
+        [1, 3]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``collect``.
+    """
+    return list(itermap(collection, iteratee))
+
+
+def nest(collection: t.Iterable[t.Any], *properties: t.Any) -> t.Any:
+    """
+    This method is like :func:`group_by` except that it supports nested grouping by multiple string
+    `properties`. If only a single key is given, it is like calling ``group_by(collection, prop)``.
+
+    Args:
+        collection: Collection to iterate over.
+        *properties: Properties to nest by.
+
+    Returns:
+        Results of nested grouping by `properties`.
+
+    Example:
+
+        >>> results = nest([{'shape': 'square', 'color': 'red', 'qty': 5},\
+                            {'shape': 'square', 'color': 'blue', 'qty': 10},\
+                            {'shape': 'square', 'color': 'orange', 'qty': 5},\
+                            {'shape': 'circle', 'color': 'yellow', 'qty': 5},\
+                            {'shape': 'circle', 'color': 'pink', 'qty': 10},\
+                            {'shape': 'oval', 'color': 'purple', 'qty': 5}],\
+                           'shape', 'qty')
+        >>> expected = {\
+            'square': {5: [{'shape': 'square', 'color': 'red', 'qty': 5},\
+                           {'shape': 'square', 'color': 'orange', 'qty': 5}],\
+                       10: [{'shape': 'square', 'color': 'blue', 'qty': 10}]},\
+            'circle': {5: [{'shape': 'circle', 'color': 'yellow', 'qty': 5}],\
+                       10: [{'shape': 'circle', 'color': 'pink', 'qty': 10}]},\
+            'oval': {5: [{'shape': 'oval', 'color': 'purple', 'qty': 5}]}}
+        >>> results == expected
+        True
+
+    .. versionadded:: 4.3.0
+    """
+    if not properties:
+        return collection
+
+    flat_properties = pyd.flatten(properties)
+    first, rest = flat_properties[0], flat_properties[1:]
+
+    return pyd.map_values(group_by(collection, first), lambda value: nest(value, *rest))
+
+
+@t.overload
+def order_by(
+    collection: t.Mapping[t.Any, T2],
+    keys: t.Iterable[t.Union[str, int]],
+    orders: t.Union[t.Iterable[bool], bool],
+    reverse: bool = False,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def order_by(
+    collection: t.Mapping[t.Any, T2],
+    keys: t.Iterable[str],
+    orders: None = None,
+    reverse: bool = False,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def order_by(
+    collection: t.Iterable[T],
+    keys: t.Iterable[t.Union[str, int]],
+    orders: t.Union[t.Iterable[bool], bool],
+    reverse: bool = False,
+) -> t.List[T]: ...
+
+
+@t.overload
+def order_by(
+    collection: t.Iterable[T],
+    keys: t.Iterable[str],
+    orders: None = None,
+    reverse: bool = False,
+) -> t.List[T]: ...
+
+
+def order_by(collection, keys, orders=None, reverse=False):
+    """
+    This method is like :func:`sort_by` except that it sorts by key names instead of an iteratee
+    function. Keys can be sorted in descending order by prepending a ``"-"`` to the key name (e.g.
+    ``"name"`` would become ``"-name"``) or by passing a list of boolean sort options via `orders`
+    where ``True`` is ascending and ``False`` is descending.
+
+    Args:
+        collection: Collection to iterate over.
+        keys: List of keys to sort by. By default, keys will be sorted in ascending order. To
+            sort a key in descending order, prepend a ``"-"`` to the key name. For example, to sort
+            the key value for ``"name"`` in descending order, use ``"-name"``.
+        orders: List of boolean sort orders to apply for each key. ``True``
+            corresponds to ascending order while ``False`` is descending. Defaults to ``None``.
+        reverse (bool, optional): Whether to reverse the sort. Defaults to ``False``.
+
+    Returns:
+        Sorted list.
+
+    Example:
+
+        >>> items = [{'a': 2, 'b': 1}, {'a': 3, 'b': 2}, {'a': 1, 'b': 3}]
+        >>> results = order_by(items, ['b', 'a'])
+        >>> assert results == [{'a': 2, 'b': 1},\
+                               {'a': 3, 'b': 2},\
+                               {'a': 1, 'b': 3}]
+        >>> results = order_by(items, ['a', 'b'])
+        >>> assert results == [{'a': 1, 'b': 3},\
+                               {'a': 2, 'b': 1},\
+                               {'a': 3, 'b': 2}]
+        >>> results = order_by(items, ['-a', 'b'])
+        >>> assert results == [{'a': 3, 'b': 2},\
+                               {'a': 2, 'b': 1},\
+                               {'a': 1, 'b': 3}]
+        >>> results = order_by(items, ['a', 'b'], [False, True])
+        >>> assert results == [{'a': 3, 'b': 2},\
+                               {'a': 2, 'b': 1},\
+                               {'a': 1, 'b': 3}]
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 3.2.0
+        Added `orders` argument.
+
+    .. versionchanged:: 3.2.0
+        Added :func:`sort_by_order` as alias.
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``order_by`` to ``order_by`` and removed alias
+        ``sort_by_order``.
+    """
+    if isinstance(collection, dict):
+        collection = collection.values()
+
+    # Maintain backwards compatibility.
+    if pyd.is_boolean(orders):
+        reverse = orders
+        orders = None
+
+    comparers = []
+
+    if orders:
+        for i, key in enumerate(keys):
+            if pyd.has(orders, i):
+                order = 1 if orders[i] else -1
+            else:
+                order = 1
+
+            comparers.append((pyd.property_(key), order))
+    else:
+        for key in keys:
+            if key.startswith("-"):
+                order = -1
+                key = key[1:]
+            else:
+                order = 1
+
+            comparers.append((pyd.property_(key), order))
+
+    def comparison(left, right):
+        # pylint: disable=useless-else-on-loop,missing-docstring
+        for func, mult in comparers:
+            result = cmp(func(left), func(right))
+            if result:
+                return mult * result
+        return 0
+
+    return sorted(collection, key=cmp_to_key(comparison), reverse=reverse)
+
+
+@t.overload
+def partition(
+    collection: t.Mapping[T, T2], predicate: t.Callable[[T2, T, t.Dict[T, T2]], t.Any]
+) -> t.List[t.List[T2]]: ...
+
+
+@t.overload
+def partition(
+    collection: t.Mapping[T, T2], predicate: t.Callable[[T2, T], t.Any]
+) -> t.List[t.List[T2]]: ...
+
+
+@t.overload
+def partition(
+    collection: t.Mapping[t.Any, T2], predicate: t.Callable[[T2], t.Any]
+) -> t.List[t.List[T2]]: ...
+
+
+@t.overload
+def partition(
+    collection: t.Mapping[t.Any, T2], predicate: t.Union[IterateeObjT, None] = None
+) -> t.List[t.List[T2]]: ...
+
+
+@t.overload
+def partition(
+    collection: t.Iterable[T], predicate: t.Callable[[T, int, t.List[T]], t.Any]
+) -> t.List[t.List[T]]: ...
+
+
+@t.overload
+def partition(
+    collection: t.Iterable[T], predicate: t.Callable[[T, int], t.Any]
+) -> t.List[t.List[T]]: ...
+
+
+@t.overload
+def partition(
+    collection: t.Iterable[T], predicate: t.Callable[[T], t.Any]
+) -> t.List[t.List[T]]: ...
+
+
+@t.overload
+def partition(
+    collection: t.Iterable[T], predicate: t.Union[IterateeObjT, None] = None
+) -> t.List[t.List[T]]: ...
+
+
+def partition(collection, predicate=None):
+    """
+    Creates an array of elements split into two groups, the first of which contains elements the
+    `predicate` returns truthy for, while the second of which contains elements the `predicate`
+    returns falsey for. The `predicate` is invoked with three arguments: ``(value, index|key,
+    collection)``.
+
+    If a property name is provided for `predicate` the created :func:`pluck` style predicate returns
+    the property value of the given element.
+
+    If an object is provided for `predicate` the created :func:`.matches` style predicate returns
+    ``True`` for elements that have the properties of the given object, else ``False``.
+
+    Args:
+        collection: Collection to iterate over.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        List of grouped elements.
+
+    Example:
+
+        >>> partition([1, 2, 3, 4], lambda x: x >= 3)
+        [[3, 4], [1, 2]]
+
+    .. versionadded:: 1.1.0
+    """
+    trues = []
+    falses = []
+
+    for is_true, value, _, _ in iteriteratee(collection, predicate):
+        if is_true:
+            trues.append(value)
+        else:
+            falses.append(value)
+
+    return [trues, falses]
+
+
+def pluck(collection: t.Iterable[t.Any], path: PathT) -> t.List[t.Any]:
+    """
+    Retrieves the value of a specified property from all elements in the collection.
+
+    Args:
+        collection: List of dicts.
+        path: Collection's path to pluck
+
+    Returns:
+        Plucked list.
+
+    Example:
+
+        >>> pluck([{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 5, "b": 6}], "a")
+        [1, 3, 5]
+        >>> pluck([[[0, 1]], [[2, 3]], [[4, 5]]], "0.1")
+        [1, 3, 5]
+        >>> pluck([{"a": {"b": 1}}, {"a": {"b": 2}}], "a.b")
+        [1, 2]
+        >>> pluck([{"a": {"b": [0, 1]}}, {"a": {"b": [2, 3]}}], "a.b.1")
+        [1, 3]
+        >>> pluck([{"a": {"b": [0, 1]}}, {"a": {"b": [2, 3]}}], ["a", "b", 1])
+        [1, 3]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Function removed.
+
+    .. versionchanged:: 4.0.1
+        Made property access deep.
+    """
+    return map_(collection, pyd.property_(path))
+
+
+@t.overload
+def reduce_(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Callable[[T3, T2, T], T3],
+    accumulator: T3,
+) -> T3: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T3, T2], T3],
+    accumulator: T3,
+) -> T3: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Mapping[t.Any, t.Any],
+    iteratee: t.Callable[[T3], T3],
+    accumulator: T3,
+) -> T3: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Callable[[T2, T2, T], T2],
+    accumulator: None = None,
+) -> T2: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T2, T2], T2],
+    accumulator: None = None,
+) -> T2: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Mapping[t.Any, t.Any],
+    iteratee: t.Callable[[T], T],
+    accumulator: None = None,
+) -> T: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T2, T, int], T2],
+    accumulator: T2,
+) -> T2: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T2, T], T2],
+    accumulator: T2,
+) -> T2: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Iterable[t.Any],
+    iteratee: t.Callable[[T2], T2],
+    accumulator: T2,
+) -> T2: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T, T, int], T],
+    accumulator: None = None,
+) -> T: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T, T], T],
+    accumulator: None = None,
+) -> T: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Iterable[t.Any],
+    iteratee: t.Callable[[T], T],
+    accumulator: None = None,
+) -> T: ...
+
+
+@t.overload
+def reduce_(
+    collection: t.Iterable[T], iteratee: None = None, accumulator: t.Union[T, None] = None
+) -> T: ...
+
+
+def reduce_(collection, iteratee=None, accumulator=None):
+    """
+    Reduces a collection to a value which is the accumulated result of running each element in the
+    collection through the iteratee, where each successive iteratee execution consumes the return
+    value of the previous execution.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+        accumulator: Initial value of aggregator. Default is to use the result of
+            the first iteration.
+
+    Returns:
+        Accumulator object containing results of reduction.
+
+    Example:
+
+        >>> reduce_([1, 2, 3, 4], lambda total, x: total * x)
+        24
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed aliases ``foldl`` and ``inject``.
+    """
+    iterable = iterator(collection)
+
+    if accumulator is None:
+        try:
+            _, accumulator = next(iterable)
+        except StopIteration as exc:
+            raise TypeError("reduce_() of empty sequence with no initial value") from exc
+
+    result = accumulator
+
+    if iteratee is None:
+        iteratee = pyd.identity
+        argcount = 1
+    else:
+        argcount = getargcount(iteratee, maxargs=3)
+
+    for index, item in iterable:
+        result = callit(iteratee, result, item, index, argcount=argcount)
+
+    return result
+
+
+@t.overload
+def reduce_right(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Callable[[T3, T2, T], T3],
+    accumulator: T3,
+) -> T3: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T3, T2], T3],
+    accumulator: T3,
+) -> T3: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Mapping[t.Any, t.Any],
+    iteratee: t.Callable[[T3], T3],
+    accumulator: T3,
+) -> T3: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Callable[[T2, T2, T], T2],
+    accumulator: None = None,
+) -> T2: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T2, T2], T2],
+    accumulator: None = None,
+) -> T2: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Mapping[t.Any, t.Any],
+    iteratee: t.Callable[[T], T],
+    accumulator: None = None,
+) -> T: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T2, T, int], T2],
+    accumulator: T2,
+) -> T2: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T2, T], T2],
+    accumulator: T2,
+) -> T2: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Iterable[t.Any],
+    iteratee: t.Callable[[T2], T2],
+    accumulator: T2,
+) -> T2: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T, T, int], T],
+    accumulator: None = None,
+) -> T: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T, T], T],
+    accumulator: None = None,
+) -> T: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Iterable[t.Any],
+    iteratee: t.Callable[[T], T],
+    accumulator: None = None,
+) -> T: ...
+
+
+@t.overload
+def reduce_right(
+    collection: t.Iterable[T], iteratee: None = None, accumulator: t.Union[T, None] = None
+) -> T: ...
+
+
+def reduce_right(collection, iteratee=None, accumulator=None):
+    """
+    This method is like :func:`reduce_` except that it iterates over elements of a `collection` from
+    right to left.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+        accumulator: Initial value of aggregator. Default is to use the result of
+            the first iteration.
+
+    Returns:
+        Accumulator object containing results of reduction.
+
+    Example:
+
+        >>> reduce_right([1, 2, 3, 4], lambda total, x: total**x)
+        4096
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 3.2.1
+        Fix bug where collection was not reversed correctly.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``foldr``.
+    """
+    if not isinstance(collection, dict):
+        collection = list(collection)[::-1]
+
+    return reduce_(collection, iteratee, accumulator)
+
+
+@t.overload
+def reductions(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Callable[[T3, T2, T], T3],
+    accumulator: T3,
+    from_right: bool = False,
+) -> t.List[T3]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T3, T2], T3],
+    accumulator: T3,
+    from_right: bool = False,
+) -> t.List[T3]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Mapping[t.Any, t.Any],
+    iteratee: t.Callable[[T3], T3],
+    accumulator: T3,
+    from_right: bool = False,
+) -> t.List[T3]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Callable[[T2, T2, T], T2],
+    accumulator: None = None,
+    from_right: bool = False,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T2, T2], T2],
+    accumulator: None = None,
+    from_right: bool = False,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Mapping[t.Any, t.Any],
+    iteratee: t.Callable[[T], T],
+    accumulator: None = None,
+    from_right: bool = False,
+) -> t.List[T]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T2, T, int], T2],
+    accumulator: T2,
+    from_right: bool = False,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T2, T], T2],
+    accumulator: T2,
+    from_right: bool = False,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Iterable[t.Any],
+    iteratee: t.Callable[[T2], T2],
+    accumulator: T2,
+    from_right: bool = False,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T, T, int], T],
+    accumulator: None = None,
+    from_right: bool = False,
+) -> t.List[T]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T, T], T],
+    accumulator: None = None,
+    from_right: bool = False,
+) -> t.List[T]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Iterable[t.Any],
+    iteratee: t.Callable[[T], T],
+    accumulator: None = None,
+    from_right: bool = False,
+) -> t.List[T]: ...
+
+
+@t.overload
+def reductions(
+    collection: t.Iterable[T],
+    iteratee: None = None,
+    accumulator: t.Union[T, None] = None,
+    from_right: bool = False,
+) -> t.List[T]: ...
+
+
+def reductions(collection, iteratee=None, accumulator=None, from_right=False):
+    """
+    This function is like :func:`reduce_` except that it returns a list of every intermediate value
+    in the reduction operation.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+        accumulator: Initial value of aggregator. Default is to use the result of
+            the first iteration.
+
+    Returns:
+        Results of each reduction operation.
+
+    Example:
+
+        >>> reductions([1, 2, 3, 4], lambda total, x: total * x)
+        [2, 6, 24]
+
+    Note:
+        The last element of the returned list would be the result of using
+        :func:`reduce_`.
+
+    .. versionadded:: 2.0.0
+    """
+    if iteratee is None:
+        iteratee = pyd.identity
+        argcount = 1
+    else:
+        argcount = getargcount(iteratee, maxargs=3)
+
+    results = []
+
+    def interceptor(result, item, index):
+        result = callit(iteratee, result, item, index, argcount=argcount)
+        results.append(result)
+        return result
+
+    reducer = reduce_right if from_right else reduce_
+    reducer(collection, interceptor, accumulator)
+
+    return results
+
+
+@t.overload
+def reductions_right(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Callable[[T3, T2, T], T3],
+    accumulator: T3,
+) -> t.List[T3]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T3, T2], T3],
+    accumulator: T3,
+) -> t.List[T3]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Mapping[t.Any, t.Any],
+    iteratee: t.Callable[[T3], T3],
+    accumulator: T3,
+) -> t.List[T3]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Callable[[T2, T2, T], T2],
+    accumulator: None = None,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T2, T2], T2],
+    accumulator: None = None,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Mapping[t.Any, t.Any],
+    iteratee: t.Callable[[T], T],
+    accumulator: None = None,
+) -> t.List[T]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T2, T, int], T2],
+    accumulator: T2,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T2, T], T2],
+    accumulator: T2,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Iterable[t.Any],
+    iteratee: t.Callable[[T2], T2],
+    accumulator: T2,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T, T, int], T],
+    accumulator: None = None,
+) -> t.List[T]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Iterable[T],
+    iteratee: t.Callable[[T, T], T],
+    accumulator: None = None,
+) -> t.List[T]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Iterable[t.Any],
+    iteratee: t.Callable[[T], T],
+    accumulator: None = None,
+) -> t.List[T]: ...
+
+
+@t.overload
+def reductions_right(
+    collection: t.Iterable[T], iteratee: None = None, accumulator: t.Union[T, None] = None
+) -> t.List[T]: ...
+
+
+def reductions_right(collection, iteratee=None, accumulator=None):
+    """
+    This method is like :func:`reductions` except that it iterates over elements of a `collection`
+    from right to left.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+        accumulator: Initial value of aggregator. Default is to use the result of
+            the first iteration.
+
+    Returns:
+        Results of each reduction operation.
+
+    Example:
+
+        >>> reductions_right([1, 2, 3, 4], lambda total, x: total**x)
+        [64, 4096, 4096]
+
+    Note:
+        The last element of the returned list would be the result of using
+        :func:`reduce_`.
+
+    .. versionadded:: 2.0.0
+    """
+    return reductions(collection, iteratee, accumulator, from_right=True)
+
+
+@t.overload
+def reject(
+    collection: t.Mapping[T, T2],
+    predicate: t.Union[t.Callable[[T2, T, t.Dict[T, T2]], t.Any], IterateeObjT, None] = None,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reject(
+    collection: t.Mapping[T, T2],
+    predicate: t.Union[t.Callable[[T2, T], t.Any], IterateeObjT, None] = None,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reject(
+    collection: t.Mapping[t.Any, T2],
+    predicate: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def reject(
+    collection: t.Iterable[T],
+    predicate: t.Union[t.Callable[[T, int, t.List[T]], t.Any], IterateeObjT, None] = None,
+) -> t.List[T]: ...
+
+
+@t.overload
+def reject(
+    collection: t.Iterable[T],
+    predicate: t.Union[t.Callable[[T, int], t.Any], IterateeObjT, None] = None,
+) -> t.List[T]: ...
+
+
+@t.overload
+def reject(
+    collection: t.Iterable[T],
+    predicate: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+) -> t.List[T]: ...
+
+
+def reject(collection, predicate=None):
+    """
+    The opposite of :func:`filter_` this method returns the elements of a collection that the
+    predicate does **not** return truthy for.
+
+    Args:
+        collection: Collection to iterate over.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        Rejected elements of `collection`.
+
+    Example:
+
+        >>> reject([1, 2, 3, 4], lambda x: x >= 3)
+        [1, 2]
+        >>> reject([{"a": 0}, {"a": 1}, {"a": 2}], "a")
+        [{'a': 0}]
+        >>> reject([{"a": 0}, {"a": 1}, {"a": 2}], {"a": 1})
+        [{'a': 0}, {'a': 2}]
+
+    .. versionadded:: 1.0.0
+    """
+    return [value for is_true, value, _, _ in iteriteratee(collection, predicate) if not is_true]
+
+
+def sample(collection: t.Sequence[T]) -> T:
+    """
+    Retrieves a random element from a given `collection`.
+
+    Args:
+        collection: Collection to iterate over.
+
+    Returns:
+        Random element from the given collection.
+
+    Example:
+
+        >>> items = [1, 2, 3, 4, 5]
+        >>> results = sample(items)
+        >>> assert results in items
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Moved multiple samples functionality to :func:`sample_size`. This
+        function now only returns a single random sample.
+    """
+    return random.choice(collection)
+
+
+def sample_size(collection: t.Sequence[T], n: t.Union[int, None] = None) -> t.List[T]:
+    """
+    Retrieves list of `n` random elements from a collection.
+
+    Args:
+        collection: Collection to iterate over.
+        n: Number of random samples to return.
+
+    Returns:
+        List of `n` sampled collection values.
+
+    Examples:
+
+        >>> items = [1, 2, 3, 4, 5]
+        >>> results = sample_size(items, 2)
+        >>> assert len(results) == 2
+        >>> assert set(items).intersection(results) == set(results)
+
+    .. versionadded:: 4.0.0
+    """
+    num = min(n or 1, len(collection))
+    return random.sample(collection, num)
+
+
+@t.overload
+def shuffle(collection: t.Mapping[t.Any, T]) -> t.List[T]: ...
+
+
+@t.overload
+def shuffle(collection: t.Iterable[T]) -> t.List[T]: ...
+
+
+def shuffle(collection):
+    """
+    Creates a list of shuffled values, using a version of the Fisher-Yates shuffle.
+
+    Args:
+        collection: Collection to iterate over.
+
+    Returns:
+        Shuffled list of values.
+
+    Example:
+
+        >>> items = [1, 2, 3, 4]
+        >>> results = shuffle(items)
+        >>> assert len(results) == len(items)
+        >>> assert set(results) == set(items)
+
+    .. versionadded:: 1.0.0
+    """
+    if isinstance(collection, dict):
+        collection = collection.values()
+
+    # Make copy of collection since random.shuffle works on list in-place.
+    collection = list(collection)
+
+    # NOTE: random.shuffle uses Fisher-Yates.
+    random.shuffle(collection)
+
+    return collection
+
+
+def size(collection: t.Sized) -> int:
+    """
+    Gets the size of the `collection` by returning `len(collection)` for iterable objects.
+
+    Args:
+        collection: Collection to iterate over.
+
+    Returns:
+        Collection length.
+
+    Example:
+
+        >>> size([1, 2, 3, 4])
+        4
+
+    .. versionadded:: 1.0.0
+    """
+    return len(collection)
+
+
+def some(
+    collection: t.Iterable[T], predicate: t.Union[t.Callable[[T], t.Any], None] = None
+) -> bool:
+    """
+    Checks if the predicate returns a truthy value for any element of a collection. The predicate is
+    invoked with three arguments: ``(value, index|key, collection)``. If a property name is passed
+    for predicate, the created :func:`map_` style predicate will return the property value of the
+    given element. If an object is passed for predicate, the created :func:`.matches` style
+    predicate will return ``True`` for elements that have the properties of the given object, else
+    ``False``.
+
+    Args:
+        collection: Collection to iterate over.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        Whether any of the elements are truthy.
+
+    Example:
+
+        >>> some([False, True, 0])
+        True
+        >>> some([False, 0, None])
+        False
+        >>> some([1, 2, 3, 4], lambda x: x >= 3)
+        True
+        >>> some([1, 2, 3, 4], lambda x: x == 0)
+        False
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``any_``.
+    """
+    if predicate:
+        cbk = pyd.iteratee(predicate)
+        collection = (cbk(item) for item in collection)
+
+    return any(collection)
+
+
+@t.overload
+def sort_by(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Union[t.Callable[[T2], t.Any], IterateeObjT, None] = None,
+    reverse: bool = False,
+) -> t.List[T2]: ...
+
+
+@t.overload
+def sort_by(
+    collection: t.Iterable[T],
+    iteratee: t.Union[t.Callable[[T], t.Any], IterateeObjT, None] = None,
+    reverse: bool = False,
+) -> t.List[T]: ...
+
+
+def sort_by(collection, iteratee=None, reverse=False):
+    """
+    Creates a list of elements, sorted in ascending order by the results of running each element in
+    a `collection` through the iteratee.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+        reverse: Whether to reverse the sort. Defaults to ``False``.
+
+    Returns:
+        Sorted list.
+
+    Example:
+
+        >>> sort_by({"a": 2, "b": 3, "c": 1})
+        [1, 2, 3]
+        >>> sort_by({"a": 2, "b": 3, "c": 1}, reverse=True)
+        [3, 2, 1]
+        >>> sort_by([{"a": 2}, {"a": 3}, {"a": 1}], "a")
+        [{'a': 1}, {'a': 2}, {'a': 3}]
+
+    .. versionadded:: 1.0.0
+    """
+    if isinstance(collection, dict):
+        collection = collection.values()
+
+    return sorted(collection, key=pyd.iteratee(iteratee), reverse=reverse)
+
+
+#
+# Utility methods not a part of the main API
+#
+
+
+def itermap(
+    collection: t.Iterable[t.Any],
+    iteratee: t.Union[t.Callable[..., t.Any], IterateeObjT, None] = None,
+) -> t.Generator[t.Any, None, None]:
+    """Generative mapper."""
+    for result in iteriteratee(collection, iteratee):
+        yield result[0]
diff --git a/.venv/lib/python3.12/site-packages/pydash/exceptions.py b/.venv/lib/python3.12/site-packages/pydash/exceptions.py
new file mode 100644
index 00000000..e4d11ec9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/exceptions.py
@@ -0,0 +1,22 @@
+"""
+Exception classes.
+
+.. versionadded:: 1.0.0
+"""
+
+from __future__ import annotations
+
+
+__all__ = ("InvalidMethod",)
+
+
+# NOTE: This needs to subclass AttributeError due to compatibility with typing.Protocol and
+#  runtime_checkable. See https://github.com/dgilland/pydash/issues/165
+class InvalidMethod(AttributeError):
+    """
+    Raised when an invalid pydash method is invoked through :func:`pydash.chaining.chain`.
+
+    .. versionadded:: 1.0.0
+    """
+
+    pass
diff --git a/.venv/lib/python3.12/site-packages/pydash/functions.py b/.venv/lib/python3.12/site-packages/pydash/functions.py
new file mode 100644
index 00000000..30a8a607
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/functions.py
@@ -0,0 +1,1461 @@
+"""
+Functions that wrap other functions.
+
+.. versionadded:: 1.0.0
+"""
+
+from __future__ import annotations
+
+from functools import cached_property
+from inspect import getfullargspec
+import itertools
+import time
+import typing as t
+
+from typing_extensions import Concatenate, Literal, ParamSpec, Protocol
+
+import pydash as pyd
+from pydash.helpers import getargcount
+
+
+__all__ = (
+    "after",
+    "ary",
+    "before",
+    "conjoin",
+    "curry",
+    "curry_right",
+    "debounce",
+    "delay",
+    "disjoin",
+    "flip",
+    "flow",
+    "flow_right",
+    "iterated",
+    "juxtapose",
+    "negate",
+    "once",
+    "over_args",
+    "partial",
+    "partial_right",
+    "rearg",
+    "spread",
+    "throttle",
+    "unary",
+    "wrap",
+)
+
+T = t.TypeVar("T")
+T1 = t.TypeVar("T1")
+T2 = t.TypeVar("T2")
+T3 = t.TypeVar("T3")
+T4 = t.TypeVar("T4")
+T5 = t.TypeVar("T5")
+P = ParamSpec("P")
+
+
+class _WithArgCount(Protocol):
+    func: t.Callable[..., t.Any]
+
+    @cached_property
+    def _argcount(self) -> t.Optional[int]:
+        return getargcount(self.func, None)
+
+
+class After(_WithArgCount, t.Generic[P, T]):
+    """Wrap a function in an after context."""
+
+    def __init__(self, func: t.Callable[P, T], n: t.SupportsInt) -> None:
+        try:
+            n = int(n)
+            assert n >= 0
+        except (ValueError, TypeError, AssertionError):
+            n = 0
+
+        self.n = n
+        self.func = func
+
+    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> t.Union[T, None]:
+        """Return results of :attr:`func` after :attr:`n` calls."""
+        self.n -= 1
+
+        if self.n <= 0:
+            return self.func(*args, **kwargs)
+
+        return None
+
+
+class Ary(_WithArgCount, t.Generic[T]):
+    """Wrap a function in an ary context."""
+
+    def __init__(self, func: t.Callable[..., T], n: t.Union[t.SupportsInt, None]) -> None:
+        try:
+            # Type error would be caught
+            n = int(n)  # type: ignore
+            assert n >= 0
+        except (ValueError, TypeError, AssertionError):
+            n = None
+
+        self.n = n
+        self.func = func
+
+    def __call__(self, *args: t.Any, **kwargs: t.Any) -> T:
+        """
+        Return results of :attr:`func` with arguments capped to :attr:`n`.
+
+        Only positional arguments are capped. Any number of keyword arguments are allowed.
+        """
+        cut_args = args[: self.n] if self.n is not None else args
+
+        return self.func(*cut_args, **kwargs)  # type: ignore
+
+
+class Before(After[P, T], t.Generic[P, T]):
+    """Wrap a function in a before context."""
+
+    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> t.Union[T, None]:
+        self.n -= 1
+
+        if self.n > 0:
+            return self.func(*args, **kwargs)
+
+        return None
+
+
+class Flow(t.Generic[P, T]):
+    """Wrap a function in a flow context."""
+
+    @t.overload
+    def __init__(
+        self,
+        func1: t.Callable[P, T2],
+        func2: t.Callable[[T2], T3],
+        func3: t.Callable[[T3], T4],
+        func4: t.Callable[[T4], T5],
+        func5: t.Callable[[T5], T],
+        *,
+        from_right: bool = True,
+    ) -> None: ...
+
+    @t.overload
+    def __init__(
+        self,
+        func1: t.Callable[P, T2],
+        func2: t.Callable[[T2], T3],
+        func3: t.Callable[[T3], T4],
+        func4: t.Callable[[T4], T],
+        *,
+        from_right: bool = True,
+    ) -> None: ...
+
+    @t.overload
+    def __init__(
+        self,
+        func1: t.Callable[P, T2],
+        func2: t.Callable[[T2], T3],
+        func3: t.Callable[[T3], T],
+        *,
+        from_right: bool = True,
+    ) -> None: ...
+
+    @t.overload
+    def __init__(
+        self, func1: t.Callable[P, T2], func2: t.Callable[[T2], T], *, from_right: bool = True
+    ) -> None: ...
+
+    @t.overload
+    def __init__(self, func1: t.Callable[P, T], *, from_right: bool = True) -> None: ...
+
+    def __init__(self, *funcs, from_right: bool = True) -> None:  # type: ignore
+        self.funcs = funcs
+        self._from_index = -1 if from_right else 0
+
+    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
+        """Return results of composing :attr:`funcs`."""
+        funcs = list(self.funcs)
+
+        result = None
+
+        while funcs:
+            result = funcs.pop(self._from_index)(*args, **kwargs)
+            # Incompatible type in assignements but needed here
+            # type safety is ensured from the `__init__` signature
+            args = (result,)  # type: ignore
+            kwargs = {}  # type: ignore
+
+        # type safety is ensured from the `__init__` signature
+        return result  # type: ignore
+
+    @cached_property
+    def _argcount(self) -> t.Optional[int]:
+        return getargcount(self.funcs[self._from_index], None)
+
+
+class Conjoin(t.Generic[T]):
+    """Wrap a set of functions in a conjoin context."""
+
+    def __init__(self, *funcs: t.Callable[[T], t.Any]) -> None:
+        self.funcs = funcs
+
+    def __call__(self, obj: t.Iterable[T]) -> bool:
+        """Return result of conjoin `obj` with :attr:`funcs` predicates."""
+
+        def iteratee(item: T) -> bool:
+            return pyd.every(self.funcs, lambda func: func(item))
+
+        return pyd.every(obj, iteratee)
+
+
+class Curry(t.Generic[T1, T]):
+    """Wrap a function in a curry context."""
+
+    def __init__(self, func, arity, args=None, kwargs=None) -> None:
+        self.func = func
+        self.arity = len(getfullargspec(func).args) if arity is None else arity
+        self.args = () if args is None else args
+        self.kwargs = {} if kwargs is None else kwargs
+
+    def __call__(self, *args, **kwargs):
+        """Store `args` and `kwargs` and call :attr:`func` if we've reached or exceeded the function
+        arity."""
+        args = self.compose_args(args)
+        kwargs.update(self.kwargs)
+
+        if (len(args) + len(kwargs)) >= self.arity:
+            args_arity = self.arity - len(kwargs)
+            args = args[: (args_arity if args_arity > 0 else 0)]
+            curried = self.func(*args, **kwargs)
+        else:
+            # NOTE: Use self.__class__ so that subclasses will use their own
+            # class to generate next iteration of call.
+            curried = self.__class__(self.func, self.arity, args, kwargs)
+
+        return curried
+
+    def compose_args(self, new_args):
+        """Combine `self.args` with `new_args` and return."""
+        return tuple(list(self.args) + list(new_args))
+
+    @cached_property
+    def _argcount(self) -> t.Optional[int]:
+        argcount = self.arity - len(self.args) - len(self.kwargs)
+        return argcount if argcount >= 0 else None
+
+
+class CurryOne(Curry[T1, T]):
+    def __call__(self, arg_one: T1) -> T:
+        return super().__call__(arg_one)  # pragma: no cover
+
+
+class CurryTwo(Curry[T1, CurryOne[T2, T]]):
+    @t.overload
+    def __call__(self, arg_one: T1) -> CurryOne[T2, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T1, arg_two: T2) -> T: ...
+
+    def __call__(self, *args, **kwargs):
+        return super().__call__(*args, **kwargs)  # pragma: no cover
+
+
+class CurryThree(Curry[T1, CurryTwo[T2, T3, T]]):
+    @t.overload
+    def __call__(self, arg_one: T1) -> CurryTwo[T2, T3, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T1, arg_two: T2) -> CurryOne[T3, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T1, arg_two: T2, arg_three: T3) -> T: ...
+
+    def __call__(self, *args, **kwargs):
+        return super().__call__(*args, **kwargs)  # pragma: no cover
+
+
+class CurryFour(Curry[T1, CurryThree[T2, T3, T4, T]]):
+    @t.overload
+    def __call__(self, arg_one: T1) -> CurryThree[T2, T3, T4, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T1, arg_two: T2) -> CurryTwo[T3, T4, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T1, arg_two: T2, arg_three: T3) -> CurryOne[T4, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T1, arg_two: T2, arg_three: T3, arg_four: T4) -> T: ...
+
+    def __call__(self, *args, **kwargs):
+        return super().__call__(*args, **kwargs)  # pragma: no cover
+
+
+class CurryFive(Curry[T1, CurryFour[T2, T3, T4, T5, T]]):
+    @t.overload
+    def __call__(self, arg_one: T1) -> CurryFour[T2, T3, T4, T5, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T1, arg_two: T2) -> CurryThree[T3, T4, T5, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T1, arg_two: T2, arg_three: T3) -> CurryTwo[T4, T5, T]: ...
+
+    @t.overload
+    def __call__(
+        self, arg_one: T1, arg_two: T2, arg_three: T3, arg_four: T4
+    ) -> CurryOne[T5, T]: ...
+
+    @t.overload
+    def __call__(
+        self, arg_one: T1, arg_two: T2, arg_three: T3, arg_four: T4, arg_five: T5
+    ) -> T: ...
+
+    def __call__(self, *args, **kwargs):
+        return super().__call__(*args, **kwargs)  # pragma: no cover
+
+
+class CurryRight(Curry[T5, T]):
+    """Wrap a function in a curry-right context."""
+
+    def compose_args(self, new_args):
+        return tuple(list(new_args) + list(self.args))
+
+
+class CurryRightOne(CurryRight[T5, T]):
+    def __call__(self, arg_one: T5) -> T:
+        return super().__call__(arg_one)  # pragma: no cover
+
+
+class CurryRightTwo(CurryRight[T5, CurryRightOne[T4, T]]):
+    @t.overload
+    def __call__(self, arg_one: T5) -> CurryRightOne[T4, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T5, arg_two: T4) -> T: ...
+
+    def __call__(self, *args, **kwargs):
+        return super().__call__(*args, **kwargs)  # pragma: no cover
+
+
+class CurryRightThree(CurryRight[T5, CurryRightTwo[T4, T3, T]]):
+    @t.overload
+    def __call__(self, arg_one: T5) -> CurryRightTwo[T4, T3, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T5, arg_two: T4) -> CurryRightOne[T3, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T5, arg_two: T4, arg_three: T3) -> T: ...
+
+    def __call__(self, *args, **kwargs):
+        return super().__call__(*args, **kwargs)  # pragma: no cover
+
+
+class CurryRightFour(CurryRight[T5, CurryRightThree[T4, T3, T2, T]]):
+    @t.overload
+    def __call__(self, arg_one: T5) -> CurryRightThree[T4, T3, T2, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T5, arg_two: T4) -> CurryRightTwo[T3, T2, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T5, arg_two: T4, arg_three: T3) -> CurryRightOne[T2, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T5, arg_two: T4, arg_three: T3, arg_four: T2) -> T: ...
+
+    def __call__(self, *args, **kwargs):
+        return super().__call__(*args, **kwargs)  # pragma: no cover
+
+
+class CurryRightFive(CurryRight[T5, CurryRightFour[T4, T3, T2, T1, T]]):
+    @t.overload
+    def __call__(self, arg_one: T5) -> CurryRightFour[T4, T3, T2, T1, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T5, arg_two: T4) -> CurryRightThree[T3, T2, T1, T]: ...
+
+    @t.overload
+    def __call__(self, arg_one: T5, arg_two: T4, arg_three: T3) -> CurryRightTwo[T2, T1, T]: ...
+
+    @t.overload
+    def __call__(
+        self, arg_one: T5, arg_two: T4, arg_three: T3, arg_four: T2
+    ) -> CurryRightOne[T1, T]: ...
+
+    @t.overload
+    def __call__(
+        self, arg_one: T5, arg_two: T4, arg_three: T3, arg_four: T2, arg_five: T1
+    ) -> T: ...
+
+    def __call__(self, *args, **kwargs):
+        return super().__call__(*args, **kwargs)  # pragma: no cover
+
+
+class Debounce(_WithArgCount, t.Generic[P, T]):
+    """Wrap a function in a debounce context."""
+
+    def __init__(
+        self, func: t.Callable[P, T], wait: int, max_wait: t.Union[int, Literal[False]] = False
+    ) -> None:
+        self.func = func
+        self.wait = wait
+        self.max_wait = max_wait
+
+        self.last_result: t.Union[T, None] = None
+
+        # Initialize last_* times to be prior to the wait periods so that func
+        # is primed to be executed on first call.
+        self.last_call = pyd.now() - self.wait
+        self.last_execution = pyd.now() - max_wait if pyd.is_number(max_wait) else None
+
+    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
+        """
+        Execute :attr:`func` if function hasn't been called within last :attr:`wait` milliseconds or
+        in last :attr:`max_wait` milliseconds.
+
+        Return results of last successful call.
+        """
+        present = pyd.now()
+
+        if (present - self.last_call) >= self.wait or (
+            self.max_wait and (present - self.last_execution) >= self.max_wait  # type: ignore
+        ):
+            self.last_result = self.func(*args, **kwargs)
+            self.last_execution = present
+
+        self.last_call = present
+
+        # It will be set after first call, cannot be `None` anymore
+        return self.last_result  # type: ignore
+
+
+class Disjoin(t.Generic[T]):
+    """Wrap a set of functions in a disjoin context."""
+
+    def __init__(self, *funcs: t.Callable[[T], t.Any]) -> None:
+        self.funcs = funcs
+
+    def __call__(self, obj: t.Iterable[T]) -> bool:
+        """Return result of disjoin `obj` with :attr:`funcs` predicates."""
+
+        def iteratee(item: T) -> bool:
+            return pyd.some(self.funcs, lambda func: func(item))
+
+        return pyd.some(obj, iteratee)
+
+
+class Flip(_WithArgCount):
+    """Wrap a function in a flip context."""
+
+    def __init__(self, func: t.Callable[..., t.Any]) -> None:
+        self.func = func
+
+    def __call__(self, *args, **kwargs):
+        return self.func(*reversed(args), **kwargs)
+
+
+class Iterated(t.Generic[T]):
+    """Wrap a function in an iterated context."""
+
+    def __init__(self, func: t.Callable[[T], T]) -> None:
+        self.func = func
+
+    def _iteration(self, initial: T) -> t.Iterator[T]:
+        """Iterator that composing :attr:`func` with itself."""
+        value = initial
+        while True:
+            value = self.func(value)
+            yield value
+
+    def __call__(self, initial: T, n: int) -> T:
+        """Return value of calling :attr:`func` `n` times using `initial` as seed value."""
+        value = initial
+        iteration = self._iteration(value)
+
+        for _ in range(n):
+            value = next(iteration)
+
+        return value
+
+
+class Juxtapose(t.Generic[P, T]):
+    """Wrap a function in a juxtapose context."""
+
+    def __init__(self, *funcs: t.Callable[P, T]) -> None:
+        self.funcs = funcs
+
+    def __call__(self, *objs: P.args, **kwargs: P.kwargs) -> t.List[T]:
+        return [func(*objs, **kwargs) for func in self.funcs]
+
+    @cached_property
+    def _argcount(self) -> t.Optional[int]:
+        return getargcount(self.funcs[0], None) if self.funcs else None
+
+
+class OverArgs(_WithArgCount):
+    """Wrap a function in an over_args context."""
+
+    def __init__(self, func: t.Callable[..., t.Any], *transforms: t.Callable[..., t.Any]) -> None:
+        self.func = func
+        self.transforms = pyd.flatten(transforms)
+
+    def __call__(self, *args):
+        args = (self.transforms[idx](args) for idx, args in enumerate(args))
+        return self.func(*args)
+
+
+class Negate(_WithArgCount, t.Generic[P]):
+    """Wrap a function in a negate context."""
+
+    def __init__(self, func: t.Callable[P, t.Any]) -> None:
+        self.func = func
+
+    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> bool:
+        """Return negated results of calling :attr:`func`."""
+        return not self.func(*args, **kwargs)
+
+
+class Once(_WithArgCount, t.Generic[P, T]):
+    """Wrap a function in a once context."""
+
+    def __init__(self, func: t.Callable[P, T]) -> None:
+        self.func = func
+        self.result: t.Union[T, None] = None
+        self.called = False
+
+    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
+        """Return results from the first call of :attr:`func`."""
+        if not self.called:
+            self.result = self.func(*args, **kwargs)
+            self.called = True
+
+        # At this point the result will be set, cannot be `None` anymore
+        return self.result  # type: ignore
+
+
+class Partial(_WithArgCount, t.Generic[T]):
+    """Wrap a function in a partial context."""
+
+    def __init__(
+        self, func: t.Callable[..., T], args: t.Any, kwargs: t.Any = None, from_right: bool = False
+    ) -> None:
+        self.func = func
+        self.args = args
+        self.kwargs = kwargs or {}
+        self.from_right = from_right
+
+    def __call__(self, *args: t.Any, **kwargs: t.Any) -> T:
+        """
+        Return results from :attr:`func` with :attr:`args` + `args`.
+
+        Apply arguments from left or right depending on :attr:`from_right`.
+        """
+        if self.from_right:
+            args = itertools.chain(args, self.args)  # type: ignore
+        else:
+            args = itertools.chain(self.args, args)  # type: ignore
+
+        kwargs = {**self.kwargs, **kwargs}
+
+        return self.func(*args, **kwargs)
+
+    @cached_property
+    def _argcount(self) -> t.Optional[int]:
+        func_argcount = getargcount(self.func, None)
+        if func_argcount is None:
+            return None
+        argcount = func_argcount - len(self.args) - len(self.kwargs)
+        return argcount if argcount >= 0 else None
+
+
+class Rearg(_WithArgCount, t.Generic[P, T]):
+    """Wrap a function in a rearg context."""
+
+    def __init__(self, func: t.Callable[P, T], *indexes: int) -> None:
+        self.func = func
+
+        # Index `indexes` by the index value, so we can do a lookup mapping by walking the function
+        # arguments.
+        self.indexes = {
+            src_index: dest_index for dest_index, src_index in enumerate(pyd.flatten(indexes))
+        }
+
+    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
+        """Return results from :attr:`func` using rearranged arguments."""
+        reargs = {}
+        rest = []
+
+        # Walk arguments to ensure each one is added to the final argument list.
+        for src_index, arg in enumerate(args):
+            # NOTE: dest_index will range from 0 to len(indexes).
+            dest_index = self.indexes.get(src_index)
+
+            if dest_index is not None:
+                # Remap argument index.
+                reargs[dest_index] = arg
+            else:
+                # Argumnet index is not contained in `indexes` so stick in the back.
+                rest.append(arg)
+
+        args = itertools.chain((reargs[key] for key in sorted(reargs)), rest)  # type: ignore
+
+        return self.func(*args, **kwargs)
+
+
+class Spread(t.Generic[T]):
+    """Wrap a function in a spread context."""
+
+    def __init__(self, func: t.Callable[..., T]) -> None:
+        self.func = func
+
+    def __call__(self, args: t.Iterable[t.Any]) -> T:
+        """Return results from :attr:`func` using array of `args` provided."""
+        return self.func(*args)
+
+
+class Throttle(_WithArgCount, t.Generic[P, T]):
+    """Wrap a function in a throttle context."""
+
+    def __init__(self, func: t.Callable[P, T], wait: int) -> None:
+        self.func = func
+        self.wait = wait
+
+        self.last_result: t.Union[T, None] = None
+        self.last_execution = pyd.now() - self.wait
+
+    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
+        """
+        Execute :attr:`func` if function hasn't been called within last :attr:`wait` milliseconds.
+
+        Return results of last successful call.
+        """
+        present = pyd.now()
+
+        if (present - self.last_execution) >= self.wait:
+            self.last_result = self.func(*args, **kwargs)
+            self.last_execution = present
+
+        # The last result will be filled on first execution, so it is always `T`
+        return self.last_result  # type: ignore
+
+
+def after(func: t.Callable[P, T], n: t.SupportsInt) -> After[P, T]:
+    """
+    Creates a function that executes `func`, with the arguments of the created function, only after
+    being called `n` times.
+
+    Args:
+        func: Function to execute.
+        n: Number of times `func` must be called before it is executed.
+
+    Returns:
+        Function wrapped in an :class:`After` context.
+
+    Example:
+
+        >>> func = lambda a, b, c: (a, b, c)
+        >>> after_func = after(func, 3)
+        >>> after_func(1, 2, 3)
+        >>> after_func(1, 2, 3)
+        >>> after_func(1, 2, 3)
+        (1, 2, 3)
+        >>> after_func(4, 5, 6)
+        (4, 5, 6)
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 3.0.0
+        Reordered arguments to make `func` first.
+    """
+    return After(func, n)
+
+
+def ary(func: t.Callable[..., T], n: t.Union[t.SupportsInt, None]) -> Ary[T]:
+    """
+    Creates a function that accepts up to `n` arguments ignoring any additional arguments. Only
+    positional arguments are capped. All keyword arguments are allowed through.
+
+    Args:
+        func: Function to cap arguments for.
+        n: Number of arguments to accept.
+
+    Returns:
+        Function wrapped in an :class:`Ary` context.
+
+    Example:
+
+        >>> func = lambda a, b, c=0, d=5: (a, b, c, d)
+        >>> ary_func = ary(func, 2)
+        >>> ary_func(1, 2, 3, 4, 5, 6)
+        (1, 2, 0, 5)
+        >>> ary_func(1, 2, 3, 4, 5, 6, c=10, d=20)
+        (1, 2, 10, 20)
+
+    .. versionadded:: 3.0.0
+    """
+    return Ary(func, n)
+
+
+def before(func: t.Callable[P, T], n: t.SupportsInt) -> Before[P, T]:
+    """
+    Creates a function that executes `func`, with the arguments of the created function, until it
+    has been called `n` times.
+
+    Args:
+        func: Function to execute.
+        n: Number of times `func` may be executed.
+
+    Returns:
+        Function wrapped in an :class:`Before` context.
+
+    Example:
+
+        >>> func = lambda a, b, c: (a, b, c)
+        >>> before_func = before(func, 3)
+        >>> before_func(1, 2, 3)
+        (1, 2, 3)
+        >>> before_func(1, 2, 3)
+        (1, 2, 3)
+        >>> before_func(1, 2, 3)
+        >>> before_func(1, 2, 3)
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 3.0.0
+        Reordered arguments to make `func` first.
+    """
+    return Before(func, n)
+
+
+def conjoin(*funcs: t.Callable[[T], t.Any]) -> t.Callable[[t.Iterable[T]], bool]:
+    """
+    Creates a function that composes multiple predicate functions into a single predicate that tests
+    whether **all** elements of an object pass each predicate.
+
+    Args:
+        *funcs: Function(s) to conjoin.
+
+    Returns:
+        Function(s) wrapped in a :class:`Conjoin` context.
+
+    Example:
+
+        >>> conjoiner = conjoin(lambda x: isinstance(x, int), lambda x: x > 3)
+        >>> conjoiner([1, 2, 3])
+        False
+        >>> conjoiner([1.0, 2, 1])
+        False
+        >>> conjoiner([4.0, 5, 6])
+        False
+        >>> conjoiner([4, 5, 6])
+        True
+
+    .. versionadded:: 2.0.0
+    """
+    return Conjoin(*funcs)
+
+
+@t.overload
+def curry(func: t.Callable[[T1], T], arity: t.Union[int, None] = None) -> CurryOne[T1, T]: ...
+
+
+@t.overload
+def curry(
+    func: t.Callable[[T1, T2], T], arity: t.Union[int, None] = None
+) -> CurryTwo[T1, T2, T]: ...
+
+
+@t.overload
+def curry(
+    func: t.Callable[[T1, T2, T3], T], arity: t.Union[int, None] = None
+) -> CurryThree[T1, T2, T3, T]: ...
+
+
+@t.overload
+def curry(
+    func: t.Callable[[T1, T2, T3, T4], T], arity: t.Union[int, None] = None
+) -> CurryFour[T1, T2, T3, T4, T]: ...
+
+
+@t.overload
+def curry(
+    func: t.Callable[[T1, T2, T3, T4, T5], T], arity: t.Union[int, None] = None
+) -> CurryFive[T1, T2, T3, T4, T5, T]: ...
+
+
+def curry(func, arity=None):
+    """
+    Creates a function that accepts one or more arguments of `func` that when invoked either
+    executes `func` returning its result (if all `func` arguments have been provided) or returns a
+    function that accepts one or more of the remaining `func` arguments, and so on.
+
+    Args:
+        func: Function to curry.
+        arity: Number of function arguments that can be accepted by curried
+            function. Default is to use the number of arguments that are accepted by `func`.
+
+    Returns:
+        Function wrapped in a :class:`Curry` context.
+
+    Example:
+
+        >>> func = lambda a, b, c: (a, b, c)
+        >>> currier = curry(func)
+        >>> currier = currier(1)
+        >>> assert isinstance(currier, Curry)
+        >>> currier = currier(2)
+        >>> assert isinstance(currier, Curry)
+        >>> currier = currier(3)
+        >>> currier
+        (1, 2, 3)
+
+    .. versionadded:: 1.0.0
+    """
+    return Curry(func, arity)
+
+
+@t.overload
+def curry_right(
+    func: t.Callable[[T1], T], arity: t.Union[int, None] = None
+) -> CurryRightOne[T1, T]: ...
+
+
+@t.overload
+def curry_right(
+    func: t.Callable[[T1, T2], T], arity: t.Union[int, None] = None
+) -> CurryRightTwo[T2, T1, T]: ...
+
+
+@t.overload
+def curry_right(
+    func: t.Callable[[T1, T2, T3], T], arity: t.Union[int, None] = None
+) -> CurryRightThree[T3, T2, T1, T]: ...
+
+
+@t.overload
+def curry_right(
+    func: t.Callable[[T1, T2, T3, T4], T], arity: t.Union[int, None] = None
+) -> CurryRightFour[T4, T3, T2, T1, T]: ...
+
+
+@t.overload
+def curry_right(
+    func: t.Callable[[T1, T2, T3, T4, T5], T],
+) -> CurryRightFive[T5, T4, T3, T2, T1, T]: ...
+
+
+def curry_right(func, arity=None):
+    """
+    This method is like :func:`curry` except that arguments are applied to `func` in the manner of
+    :func:`partial_right` instead of :func:`partial`.
+
+    Args:
+        func: Function to curry.
+        arity: Number of function arguments that can be accepted by curried
+            function. Default is to use the number of arguments that are accepted by `func`.
+
+    Returns:
+        Function wrapped in a :class:`CurryRight` context.
+
+    Example:
+
+        >>> func = lambda a, b, c: (a, b, c)
+        >>> currier = curry_right(func)
+        >>> currier = currier(1)
+        >>> assert isinstance(currier, CurryRight)
+        >>> currier = currier(2)
+        >>> assert isinstance(currier, CurryRight)
+        >>> currier = currier(3)
+        >>> currier
+        (3, 2, 1)
+
+    .. versionadded:: 1.1.0
+    """
+    return CurryRight(func, arity)
+
+
+def debounce(
+    func: t.Callable[P, T], wait: int, max_wait: t.Union[int, Literal[False]] = False
+) -> Debounce[P, T]:
+    """
+    Creates a function that will delay the execution of `func` until after `wait` milliseconds have
+    elapsed since the last time it was invoked. Subsequent calls to the debounced function will
+    return the result of the last `func` call.
+
+    Args:
+        func: Function to execute.
+        wait: Milliseconds to wait before executing `func`.
+        max_wait (optional): Maximum time to wait before executing `func`.
+
+    Returns:
+        Function wrapped in a :class:`Debounce` context.
+
+    .. versionadded:: 1.0.0
+    """
+    return Debounce(func, wait, max_wait=max_wait)
+
+
+def delay(func: t.Callable[P, T], wait: int, *args: "P.args", **kwargs: "P.kwargs") -> T:
+    """
+    Executes the `func` function after `wait` milliseconds. Additional arguments will be provided to
+    `func` when it is invoked.
+
+    Args:
+        func: Function to execute.
+        wait: Milliseconds to wait before executing `func`.
+        *args: Arguments to pass to `func`.
+        **kwargs: Keyword arguments to pass to `func`.
+
+    Returns:
+        Return from `func`.
+
+    .. versionadded:: 1.0.0
+    """
+    time.sleep(wait / 1000.0)
+    return func(*args, **kwargs)
+
+
+def disjoin(*funcs: t.Callable[[T], t.Any]) -> Disjoin[T]:
+    """
+    Creates a function that composes multiple predicate functions into a single predicate that tests
+    whether **any** elements of an object pass each predicate.
+
+    Args:
+        *funcs: Function(s) to disjoin.
+
+    Returns:
+        Function(s) wrapped in a :class:`Disjoin` context.
+
+    Example:
+
+        >>> disjoiner = disjoin(lambda x: isinstance(x, float),\
+                                lambda x: isinstance(x, int))
+        >>> disjoiner([1, '2', '3'])
+        True
+        >>> disjoiner([1.0, '2', '3'])
+        True
+        >>> disjoiner(['1', '2', '3'])
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return Disjoin(*funcs)
+
+
+@t.overload
+def flip(func: t.Callable[[T1, T2, T3, T4, T5], T]) -> t.Callable[[T5, T4, T3, T2, T1], T]: ...
+
+
+@t.overload
+def flip(func: t.Callable[[T1, T2, T3, T4], T]) -> t.Callable[[T4, T3, T2, T1], T]: ...
+
+
+@t.overload
+def flip(func: t.Callable[[T1, T2, T3], T]) -> t.Callable[[T3, T2, T1], T]: ...
+
+
+@t.overload
+def flip(func: t.Callable[[T1, T2], T]) -> t.Callable[[T2, T1], T]: ...
+
+
+@t.overload
+def flip(func: t.Callable[[T1], T]) -> t.Callable[[T1], T]: ...
+
+
+def flip(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
+    """
+    Creates a function that invokes the method with arguments reversed.
+
+    Args:
+        func: Function to flip arguments for.
+
+    Returns:
+        Function wrapped in a :class:`Flip` context.
+
+    Example:
+
+        >>> flipped = flip(lambda *args: args)
+        >>> flipped(1, 2, 3, 4)
+        (4, 3, 2, 1)
+        >>> flipped = flip(lambda *args: [i * 2 for i in args])
+        >>> flipped(1, 2, 3, 4)
+        [8, 6, 4, 2]
+
+    .. versionadded:: 4.0.0
+    """
+    return Flip(func)
+
+
+@t.overload
+def flow(
+    func1: t.Callable[P, T2],
+    func2: t.Callable[[T2], T3],
+    func3: t.Callable[[T3], T4],
+    func4: t.Callable[[T4], T5],
+    func5: t.Callable[[T5], T],
+) -> Flow[P, T]: ...
+
+
+@t.overload
+def flow(
+    func1: t.Callable[P, T2],
+    func2: t.Callable[[T2], T3],
+    func3: t.Callable[[T3], T4],
+    func4: t.Callable[[T4], T],
+) -> Flow[P, T]: ...
+
+
+@t.overload
+def flow(
+    func1: t.Callable[P, T2],
+    func2: t.Callable[[T2], T3],
+    func3: t.Callable[[T3], T],
+) -> Flow[P, T]: ...
+
+
+@t.overload
+def flow(func1: t.Callable[P, T2], func2: t.Callable[[T2], T]) -> Flow[P, T]: ...
+
+
+@t.overload
+def flow(func1: t.Callable[P, T]) -> Flow[P, T]: ...
+
+
+def flow(*funcs):
+    """
+    Creates a function that is the composition of the provided functions, where each successive
+    invocation is supplied the return value of the previous. For example, composing the functions
+    ``f()``, ``g()``, and ``h()`` produces ``h(g(f()))``.
+
+    Args:
+        *funcs: Function(s) to compose.
+
+    Returns:
+        Function(s) wrapped in a :class:`Flow` context.
+
+    Example:
+
+        >>> mult_5 = lambda x: x * 5
+        >>> div_10 = lambda x: x / 10.0
+        >>> pow_2 = lambda x: x**2
+        >>> ops = flow(sum, mult_5, div_10, pow_2)
+        >>> ops([1, 2, 3, 4])
+        25.0
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 2.3.1
+        Added :func:`pipe` as alias.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``pipe``.
+    """
+    return Flow(*funcs, from_right=False)
+
+
+@t.overload
+def flow_right(
+    func5: t.Callable[[T4], T],
+    func4: t.Callable[[T3], T4],
+    func3: t.Callable[[T2], T3],
+    func2: t.Callable[[T1], T2],
+    func1: t.Callable[P, T1],
+) -> Flow[P, T]: ...
+
+
+@t.overload
+def flow_right(
+    func4: t.Callable[[T3], T],
+    func3: t.Callable[[T2], T3],
+    func2: t.Callable[[T1], T2],
+    func1: t.Callable[P, T1],
+) -> Flow[P, T]: ...
+
+
+@t.overload
+def flow_right(
+    func3: t.Callable[[T2], T],
+    func2: t.Callable[[T1], T2],
+    func1: t.Callable[P, T1],
+) -> Flow[P, T]: ...
+
+
+@t.overload
+def flow_right(func2: t.Callable[[T1], T], func1: t.Callable[P, T1]) -> Flow[P, T]: ...
+
+
+@t.overload
+def flow_right(func1: t.Callable[P, T]) -> Flow[P, T]: ...
+
+
+def flow_right(*funcs):
+    """
+    This function is like :func:`flow` except that it creates a function that invokes the provided
+    functions from right to left. For example, composing the functions ``f()``, ``g()``, and ``h()``
+    produces ``f(g(h()))``.
+
+    Args:
+        *funcs: Function(s) to compose.
+
+    Returns:
+        Function(s) wrapped in a :class:`Flow` context.
+
+    Example:
+
+        >>> mult_5 = lambda x: x * 5
+        >>> div_10 = lambda x: x / 10.0
+        >>> pow_2 = lambda x: x**2
+        >>> ops = flow_right(mult_5, div_10, pow_2, sum)
+        >>> ops([1, 2, 3, 4])
+        50.0
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 2.0.0
+        Added :func:`flow_right` and made :func:`compose` an alias.
+
+    .. versionchanged:: 2.3.1
+        Added :func:`pipe_right` as alias.
+
+    .. versionchanged:: 4.0.0
+        Removed aliases ``pipe_right`` and ``compose``.
+    """
+    return Flow(*funcs, from_right=True)
+
+
+def iterated(func: t.Callable[[T], T]) -> Iterated[T]:
+    """
+    Creates a function that is composed with itself. Each call to the iterated function uses the
+    previous function call's result as input. Returned :class:`Iterated` instance can be called with
+    ``(initial, n)`` where `initial` is the initial value to seed `func` with and `n` is the number
+    of times to call `func`.
+
+    Args:
+        func: Function to iterate.
+
+    Returns:
+        Function wrapped in a :class:`Iterated` context.
+
+    Example:
+
+        >>> doubler = iterated(lambda x: x * 2)
+        >>> doubler(4, 5)
+        128
+        >>> doubler(3, 9)
+        1536
+
+    .. versionadded:: 2.0.0
+    """
+    return Iterated(func)
+
+
+def juxtapose(*funcs: t.Callable[P, T]) -> Juxtapose[P, T]:
+    """
+    Creates a function whose return value is a list of the results of calling each `funcs` with the
+    supplied arguments.
+
+    Args:
+        *funcs: Function(s) to juxtapose.
+
+    Returns:
+        Function wrapped in a :class:`Juxtapose` context.
+
+    Example:
+
+        >>> double = lambda x: x * 2
+        >>> triple = lambda x: x * 3
+        >>> quadruple = lambda x: x * 4
+        >>> juxtapose(double, triple, quadruple)(5)
+        [10, 15, 20]
+
+
+    .. versionadded:: 2.0.0
+    """
+    return Juxtapose(*funcs)
+
+
+def negate(func: t.Callable[P, t.Any]) -> Negate[P]:
+    """
+    Creates a function that negates the result of the predicate `func`. The `func` function is
+    executed with the arguments of the created function.
+
+    Args:
+        func: Function to negate execute.
+
+    Returns:
+        Function wrapped in a :class:`Negate` context.
+
+    Example:
+
+        >>> not_is_number = negate(lambda x: isinstance(x, (int, float)))
+        >>> not_is_number(1)
+        False
+        >>> not_is_number("1")
+        True
+
+    .. versionadded:: 1.1.0
+    """
+    return Negate(func)
+
+
+def once(func: t.Callable[P, T]) -> Once[P, T]:
+    """
+    Creates a function that is restricted to execute `func` once. Repeat calls to the function will
+    return the value of the first call.
+
+    Args:
+        func: Function to execute.
+
+    Returns:
+        Function wrapped in a :class:`Once` context.
+
+    Example:
+
+        >>> oncer = once(lambda *args: args[0])
+        >>> oncer(5)
+        5
+        >>> oncer(6)
+        5
+
+    .. versionadded:: 1.0.0
+    """
+    return Once(func)
+
+
+@t.overload
+def over_args(
+    func: t.Callable[[T1, T2, T3, T4, T5], T],
+    transform_one: t.Callable[[T1], T1],
+    transform_two: t.Callable[[T2], T2],
+    transform_three: t.Callable[[T3], T3],
+    transform_four: t.Callable[[T4], T4],
+    transform_five: t.Callable[[T5], T5],
+) -> t.Callable[[T1, T2, T3, T4, T5], T]: ...
+
+
+@t.overload
+def over_args(
+    func: t.Callable[[T1, T2, T3, T4], T],
+    transform_one: t.Callable[[T1], T1],
+    transform_two: t.Callable[[T2], T2],
+    transform_three: t.Callable[[T3], T3],
+    transform_four: t.Callable[[T4], T4],
+) -> t.Callable[[T1, T2, T3, T4], T]: ...
+
+
+@t.overload
+def over_args(
+    func: t.Callable[[T1, T2, T3], T],
+    transform_one: t.Callable[[T1], T1],
+    transform_two: t.Callable[[T2], T2],
+    transform_three: t.Callable[[T3], T3],
+) -> t.Callable[[T1, T2, T3], T]: ...
+
+
+@t.overload
+def over_args(
+    func: t.Callable[[T1, T2], T],
+    transform_one: t.Callable[[T1], T1],
+    transform_two: t.Callable[[T2], T2],
+) -> t.Callable[[T1, T2], T]: ...
+
+
+@t.overload
+def over_args(
+    func: t.Callable[[T1], T],
+    transform_one: t.Callable[[T1], T1],
+) -> t.Callable[[T1], T]: ...
+
+
+def over_args(func, *transforms):
+    """
+    Creates a function that runs each argument through a corresponding transform function.
+
+    Args:
+        func: Function to wrap.
+        *transforms: Functions to transform arguments, specified as individual functions
+            or lists of functions.
+
+    Returns:
+        Function wrapped in a :class:`OverArgs` context.
+
+    Example:
+
+        >>> squared = lambda x: x**2
+        >>> double = lambda x: x * 2
+        >>> modder = over_args(lambda x, y: [x, y], squared, double)
+        >>> modder(5, 10)
+        [25, 20]
+
+    .. versionadded:: 3.3.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``mod_args`` to ``over_args``.
+    """
+    return OverArgs(func, *transforms)
+
+
+def partial(func: t.Callable[..., T], *args: t.Any, **kwargs: t.Any) -> Partial[T]:
+    """
+    Creates a function that, when called, invokes `func` with any additional partial arguments
+    prepended to those provided to the new function.
+
+    Args:
+        func: Function to execute.
+        *args: Partial arguments to prepend to function call.
+        **kwargs: Partial keyword arguments to bind to function call.
+
+    Returns:
+        Function wrapped in a :class:`Partial` context.
+
+    Example:
+
+        >>> dropper = partial(lambda array, n: array[n:], [1, 2, 3, 4])
+        >>> dropper(2)
+        [3, 4]
+        >>> dropper(1)
+        [2, 3, 4]
+        >>> myrest = partial(lambda array, n: array[n:], n=1)
+        >>> myrest([1, 2, 3, 4])
+        [2, 3, 4]
+
+    .. versionadded:: 1.0.0
+    """
+    return Partial(func, args, kwargs)
+
+
+def partial_right(func: t.Callable[..., T], *args: t.Any, **kwargs: t.Any) -> Partial[T]:
+    """
+    This method is like :func:`partial` except that partial arguments are appended to those provided
+    to the new function.
+
+    Args:
+        func: Function to execute.
+        *args: Partial arguments to append to function call.
+        **kwargs: Partial keyword arguments to bind to function call.
+
+    Returns:
+        Function wrapped in a :class:`Partial` context.
+
+    Example:
+
+        >>> myrest = partial_right(lambda array, n: array[n:], 1)
+        >>> myrest([1, 2, 3, 4])
+        [2, 3, 4]
+
+    .. versionadded:: 1.0.0
+    """
+    return Partial(func, args, kwargs, from_right=True)
+
+
+def rearg(func: t.Callable[P, T], *indexes: int) -> Rearg[P, T]:
+    """
+    Creates a function that invokes `func` with arguments arranged according to the specified
+    indexes where the argument value at the first index is provided as the first argument, the
+    argument value at the second index is provided as the second argument, and so on.
+
+    Args:
+        func: Function to rearrange arguments for.
+        *indexes: The arranged argument indexes.
+
+    Returns:
+        Function wrapped in a :class:`Rearg` context.
+
+    Example:
+
+        >>> jumble = rearg(lambda *args: args, 1, 2, 3)
+        >>> jumble(1, 2, 3)
+        (2, 3, 1)
+        >>> jumble("a", "b", "c", "d", "e")
+        ('b', 'c', 'd', 'a', 'e')
+
+    .. versionadded:: 3.0.0
+    """
+    return Rearg(func, *indexes)
+
+
+def spread(func: t.Callable[..., T]) -> Spread[T]:
+    """
+    Creates a function that invokes `func` with the array of arguments provided to the created
+    function.
+
+    Args:
+        func: Function to spread.
+
+    Returns:
+        Function wrapped in a :class:`Spread` context.
+
+    Example:
+
+        >>> greet = spread(lambda *people: "Hello " + ", ".join(people) + "!")
+        >>> greet(["Mike", "Don", "Leo"])
+        'Hello Mike, Don, Leo!'
+
+    .. versionadded:: 3.1.0
+    """
+    return Spread(func)
+
+
+def throttle(func: t.Callable[P, T], wait: int) -> Throttle[P, T]:
+    """
+    Creates a function that, when executed, will only call the `func` function at most once per
+    every `wait` milliseconds. Subsequent calls to the throttled function will return the result of
+    the last `func` call.
+
+    Args:
+        func: Function to throttle.
+        wait: Milliseconds to wait before calling `func` again.
+
+    Returns:
+        Results of last `func` call.
+
+    .. versionadded:: 1.0.0
+    """
+    return Throttle(func, wait)
+
+
+def unary(func: t.Callable[..., T]) -> Ary[T]:
+    """
+    Creates a function that accepts up to one argument, ignoring any additional arguments.
+
+    Args:
+        func: Function to cap arguments for.
+
+    Returns:
+        Function wrapped in an :class:`Ary` context.
+
+    Example:
+
+        >>> func = lambda a, b=1, c=0, d=5: (a, b, c, d)
+        >>> unary_func = unary(func)
+        >>> unary_func(1, 2, 3, 4, 5, 6)
+        (1, 1, 0, 5)
+        >>> unary_func(1, 2, 3, 4, 5, 6, b=0, c=10, d=20)
+        (1, 0, 10, 20)
+
+    .. versionadded:: 4.0.0
+    """
+    return Ary(func, 1)
+
+
+def wrap(value: T1, func: t.Callable[Concatenate[T1, P], T]) -> Partial[T]:
+    """
+    Creates a function that provides value to the wrapper function as its first argument. Additional
+    arguments provided to the function are appended to those provided to the wrapper function.
+
+    Args:
+        value: Value provided as first argument to function call.
+        func: Function to execute.
+
+    Returns:
+        Function wrapped in a :class:`Partial` context.
+
+    Example:
+
+        >>> wrapper = wrap("hello", lambda *args: args)
+        >>> wrapper(1, 2)
+        ('hello', 1, 2)
+
+    .. versionadded:: 1.0.0
+    """
+    return Partial(func, (value,))
diff --git a/.venv/lib/python3.12/site-packages/pydash/helpers.py b/.venv/lib/python3.12/site-packages/pydash/helpers.py
new file mode 100644
index 00000000..ee9accb1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/helpers.py
@@ -0,0 +1,327 @@
+"""Generic utility methods not part of main API."""
+
+from __future__ import annotations
+
+import builtins
+from collections.abc import Hashable, Iterable, Mapping, Sequence
+from decimal import Decimal
+from functools import wraps
+import inspect
+import operator
+import warnings
+
+import pydash as pyd
+
+
+#: Singleton object that differentiates between an explicit ``None`` value and an unset value.
+#: As a class so it has its own type
+class Unset: ...
+
+
+UNSET = Unset()
+
+#: Tuple of number types.
+NUMBER_TYPES = (int, float, Decimal)
+
+#: Dictionary of builtins with keys as the builtin function and values as the string name.
+BUILTINS = {value: key for key, value in builtins.__dict__.items() if isinstance(value, Hashable)}
+
+#: Object keys that are restricted from access via path access.
+RESTRICTED_KEYS = ("__globals__", "__builtins__")
+
+#: Inspect signature parameter kinds that correspond to positional arguments.
+POSITIONAL_PARAMETERS = (
+    inspect.Parameter.VAR_POSITIONAL,
+    inspect.Parameter.POSITIONAL_ONLY,
+    inspect.Parameter.POSITIONAL_OR_KEYWORD,
+)
+
+
+def callit(iteratee, *args, **kwargs):
+    """Inspect argspec of `iteratee` function and only pass the supported arguments when calling
+    it."""
+    maxargs = len(args)
+    argcount = kwargs["argcount"] if "argcount" in kwargs else getargcount(iteratee, maxargs)
+    argstop = min([maxargs, argcount])
+
+    return iteratee(*args[:argstop])
+
+
+def getargcount(iteratee, maxargs):
+    """Return argument count of iteratee function."""
+    if hasattr(iteratee, "_argcount"):
+        # Optimization feature where argcount of iteratee is known and properly
+        # set by initiator.
+        # It should always be right, but it can be `None` for the function wrappers
+        # in `pydash.function` as the wrapped functions are out of our control and
+        # can support an unknown number of arguments.
+        argcount = iteratee._argcount
+        return argcount if argcount is not None else maxargs
+
+    if isinstance(iteratee, type) or pyd.is_builtin(iteratee):
+        # Only pass single argument to type iteratees or builtins.
+        argcount = 1
+    else:
+        argcount = 1
+
+        try:
+            argcount = _getargcount(iteratee, maxargs)
+        except TypeError:  # pragma: no cover
+            pass
+
+    return argcount
+
+
+def _getargcount(iteratee, maxargs):
+    argcount = None
+
+    try:
+        # PY2: inspect.signature was added in Python 3.
+        # Try to use inspect.signature when possible since it works better for our purpose of
+        # getting the iteratee argcount since it takes into account the "self" argument in callable
+        # classes.
+        sig = inspect.signature(iteratee)
+    except (TypeError, ValueError, AttributeError):  # pragma: no cover
+        pass
+    else:
+        # VAR_POSITIONAL corresponds to *args so we only want to count parameters if there isn't a
+        # catch-all for positional args.
+        params = list(sig.parameters.values())
+        if not any(param.kind == inspect.Parameter.VAR_POSITIONAL for param in params):
+            positional_params = [p for p in params if p.kind in POSITIONAL_PARAMETERS]
+            argcount = len(positional_params)
+
+    if argcount is None:
+        # Signatures were added these operator methods in Python 3.12.3 and 3.11.9 but their
+        # instance objects are incorrectly reported as accepting varargs when they only accept a
+        # single argument.
+        if isinstance(iteratee, (operator.itemgetter, operator.attrgetter, operator.methodcaller)):
+            argcount = 1
+        else:
+            argspec = inspect.getfullargspec(iteratee)
+            if argspec and not argspec.varargs:  # pragma: no cover
+                # Use inspected arg count.
+                argcount = len(argspec.args)
+
+    if argcount is None:
+        # Assume all args are handleable.
+        argcount = maxargs
+
+    return argcount
+
+
+def iteriteratee(obj, iteratee=None, reverse=False):
+    """Return iterative iteratee based on collection type."""
+    if iteratee is None:
+        cbk = pyd.identity
+        argcount = 1
+    else:
+        cbk = pyd.iteratee(iteratee)
+        argcount = getargcount(cbk, maxargs=3)
+
+    items = iterator(obj)
+
+    if reverse:
+        items = reversed(tuple(items))
+
+    for key, item in items:
+        yield callit(cbk, item, key, obj, argcount=argcount), item, key, obj
+
+
+def iterator(obj):
+    """Return iterative based on object type."""
+    if isinstance(obj, Mapping):
+        return obj.items()
+    elif hasattr(obj, "iteritems"):
+        return obj.iteritems()  # noqa: B301
+    elif hasattr(obj, "items"):
+        return iter(obj.items())
+    elif isinstance(obj, Iterable):
+        return enumerate(obj)
+    else:
+        return getattr(obj, "__dict__", {}).items()
+
+
+def base_get(obj, key, default=UNSET):
+    """
+    Safely get an item by `key` from a sequence or mapping object when `default` provided.
+
+    Args:
+        obj: Sequence or mapping to retrieve item from.
+        key: Key or index identifying which item to retrieve.
+        default: Default value to return if `key` not found in `obj`.
+
+    Returns:
+        `obj[key]`, `obj.key`, or `default`.
+
+    Raises:
+        KeyError: If `obj` is missing key, index, or attribute and no default value provided.
+    """
+    if isinstance(obj, dict):
+        value = _base_get_dict(obj, key, default=default)
+    elif not isinstance(obj, (Mapping, Sequence)) or (
+        isinstance(obj, tuple) and hasattr(obj, "_fields")
+    ):
+        # Don't use getattr for dict/list objects since we don't want class methods/attributes
+        # returned for them but do allow getattr for namedtuple.
+        value = _base_get_object(obj, key, default=default)
+    else:
+        value = _base_get_item(obj, key, default=default)
+
+    if value is UNSET:
+        # Raise if there's no default provided.
+        raise KeyError(f'Object "{repr(obj)}" does not have key "{key}"')
+
+    return value
+
+
+def _base_get_dict(obj, key, default=UNSET):
+    value = obj.get(key, UNSET)
+    if value is UNSET:
+        value = default
+        if not isinstance(key, int):
+            # Try integer key fallback.
+            try:
+                value = obj.get(int(key), default)
+            except Exception:
+                pass
+    return value
+
+
+def _base_get_item(obj, key, default=UNSET):
+    try:
+        return obj[key]
+    except Exception:
+        pass
+
+    if not isinstance(key, int):
+        try:
+            return obj[int(key)]
+        except Exception:
+            pass
+
+    return default
+
+
+def _base_get_object(obj, key, default=UNSET):
+    value = _base_get_item(obj, key, default=UNSET)
+    if value is UNSET:
+        _raise_if_restricted_key(key)
+        value = default
+        try:
+            value = getattr(obj, key)
+        except Exception:
+            pass
+    return value
+
+
+def _raise_if_restricted_key(key):
+    # Prevent access to restricted keys for security reasons.
+    if key in RESTRICTED_KEYS:
+        raise KeyError(f"access to restricted key {key!r} is not allowed")
+
+
+def base_set(obj, key, value, allow_override=True):
+    """
+    Set an object's `key` to `value`. If `obj` is a ``list`` and the `key` is the next available
+    index position, append to list; otherwise, pad the list of ``None`` and then append to the list.
+
+    Args:
+        obj: Object to assign value to.
+        key: Key or index to assign to.
+        value: Value to assign.
+        allow_override: Whether to allow overriding a previously set key.
+    """
+    if isinstance(obj, dict):
+        if allow_override or key not in obj:
+            obj[key] = value
+    elif isinstance(obj, list):
+        key = int(key)
+
+        if key < len(obj):
+            if allow_override:
+                obj[key] = value
+        else:
+            if key > len(obj):
+                # Pad list object with None values up to the index key, so we can append the value
+                # into the key index.
+                obj[:] = (obj + [None] * key)[:key]
+            obj.append(value)
+    elif (allow_override or not hasattr(obj, key)) and obj is not None:
+        _raise_if_restricted_key(key)
+        setattr(obj, key, value)
+
+    return obj
+
+
+def cmp(a, b):  # pragma: no cover
+    """
+    Replacement for built-in function ``cmp`` that was removed in Python 3.
+
+    Note: Mainly used for comparison during sorting.
+    """
+    if a is None and b is None:
+        return 0
+    elif a is None:
+        return -1
+    elif b is None:
+        return 1
+    return (a > b) - (a < b)
+
+
+def parse_iteratee(iteratee_keyword, *args, **kwargs):
+    """Try to find iteratee function passed in either as a keyword argument or as the last
+    positional argument in `args`."""
+    iteratee = kwargs.get(iteratee_keyword)
+    last_arg = args[-1]
+
+    if iteratee is None and (
+        callable(last_arg) or isinstance(last_arg, (dict, str)) or last_arg is None
+    ):
+        iteratee = last_arg
+        args = args[:-1]
+
+    return iteratee, args
+
+
+class iterator_with_default(object):
+    """A wrapper around an iterator object that provides a default."""
+
+    def __init__(self, collection, default):
+        self.iter = iter(collection)
+        self.default = default
+
+    def __iter__(self):
+        return self
+
+    def next_default(self):
+        ret = self.default
+        self.default = UNSET
+        return ret
+
+    def __next__(self):
+        ret = next(self.iter, self.next_default())
+        if ret is UNSET:
+            raise StopIteration
+        return ret
+
+    next = __next__
+
+
+def deprecated(func):  # pragma: no cover
+    """
+    This is a decorator which can be used to mark functions as deprecated.
+
+    It will result in a warning being emitted when the function is used.
+    """
+
+    @wraps(func)
+    def wrapper(*args, **kwargs):
+        warnings.warn(
+            f"Call to deprecated function {func.__name__}.",
+            category=DeprecationWarning,
+            stacklevel=3,
+        )
+        return func(*args, **kwargs)
+
+    return wrapper
diff --git a/.venv/lib/python3.12/site-packages/pydash/numerical.py b/.venv/lib/python3.12/site-packages/pydash/numerical.py
new file mode 100644
index 00000000..711cab0c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/numerical.py
@@ -0,0 +1,1252 @@
+"""
+Numerical/mathematical related functions.
+
+.. versionadded:: 2.1.0
+"""
+
+from __future__ import annotations
+
+import math
+import operator
+import typing as t
+
+import pydash as pyd
+
+from .helpers import UNSET, Unset, iterator, iterator_with_default, iteriteratee
+from .types import IterateeObjT, NumberNoDecimalT, NumberT, SupportsMul, SupportsRound
+
+
+if t.TYPE_CHECKING:
+    from decimal import Decimal  # pragma: no cover
+
+    from _typeshed import SupportsAdd, SupportsRichComparisonT, SupportsSub  # pragma: no cover
+
+
+__all__ = (
+    "add",
+    "ceil",
+    "clamp",
+    "divide",
+    "floor",
+    "max_",
+    "max_by",
+    "mean",
+    "mean_by",
+    "median",
+    "min_",
+    "min_by",
+    "moving_mean",
+    "multiply",
+    "power",
+    "round_",
+    "scale",
+    "slope",
+    "std_deviation",
+    "sum_",
+    "sum_by",
+    "subtract",
+    "transpose",
+    "variance",
+    "zscore",
+)
+
+T = t.TypeVar("T")
+T2 = t.TypeVar("T2")
+T3 = t.TypeVar("T3")
+
+
+INFINITY = float("inf")
+
+
+@t.overload
+def add(a: "SupportsAdd[T, T2]", b: T) -> T2: ...
+
+
+@t.overload
+def add(a: T, b: "SupportsAdd[T, T2]") -> T2: ...
+
+
+def add(a, b):
+    """
+    Adds two numbers.
+
+    Args:
+        a: First number to add.
+        b: Second number to add.
+
+    Returns:
+        number
+
+    Example:
+
+        >>> add(10, 5)
+        15
+
+    .. versionadded:: 2.1.0
+
+    .. versionchanged:: 3.3.0
+        Support adding two numbers when passed as positional arguments.
+
+    .. versionchanged:: 4.0.0
+        Only support two argument addition.
+    """
+    return a + b
+
+
+@t.overload
+def sum_(collection: t.Mapping[t.Any, "SupportsAdd[int, T]"]) -> T: ...
+
+
+@t.overload
+def sum_(collection: t.Iterable["SupportsAdd[int, T]"]) -> T: ...
+
+
+def sum_(collection):
+    """
+    Sum each element in `collection`.
+
+    Args:
+        collection: Collection to process or first number to add.
+
+    Returns:
+        Result of summation.
+
+    Example:
+
+        >>> sum_([1, 2, 3, 4])
+        10
+
+    .. versionadded:: 2.1.0
+
+    .. versionchanged:: 3.3.0
+        Support adding two numbers when passed as positional arguments.
+
+    .. versionchanged:: 4.0.0
+        Move iteratee support to :func:`sum_by`. Move two argument addition to
+        :func:`add`.
+    """
+    return sum_by(collection)
+
+
+@t.overload
+def sum_by(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Callable[[T2, T, t.Dict[T, T2]], "SupportsAdd[int, T3]"],
+) -> T3: ...
+
+
+@t.overload
+def sum_by(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], "SupportsAdd[int, T3]"]
+) -> T3: ...
+
+
+@t.overload
+def sum_by(
+    collection: t.Mapping[t.Any, T2], iteratee: t.Callable[[T2], "SupportsAdd[int, T3]"]
+) -> T3: ...
+
+
+@t.overload
+def sum_by(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], "SupportsAdd[int, T2]"]
+) -> T2: ...
+
+
+@t.overload
+def sum_by(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int], "SupportsAdd[int, T2]"]
+) -> T2: ...
+
+
+@t.overload
+def sum_by(collection: t.Iterable[T], iteratee: t.Callable[[T], "SupportsAdd[int, T2]"]) -> T2: ...
+
+
+@t.overload
+def sum_by(collection: t.Mapping[t.Any, "SupportsAdd[int, T]"], iteratee: None = None) -> T: ...
+
+
+@t.overload
+def sum_by(collection: t.Iterable["SupportsAdd[int, T]"], iteratee: None = None) -> T: ...
+
+
+def sum_by(collection, iteratee=None):
+    """
+    Sum each element in `collection`. If iteratee is passed, each element of `collection` is passed
+    through an iteratee before the summation is computed.
+
+    Args:
+        collection: Collection to process or first number to add.
+        iteratee: Iteratee applied per iteration or second number to add.
+
+    Returns:
+        Result of summation.
+
+    Example:
+
+        >>> sum_by([1, 2, 3, 4], lambda x: x**2)
+        30
+
+    .. versionadded:: 4.0.0
+    """
+    return sum(result[0] for result in iteriteratee(collection, iteratee))
+
+
+@t.overload
+def mean(collection: t.Mapping[t.Any, "SupportsAdd[int, t.Any]"]) -> float: ...
+
+
+@t.overload
+def mean(collection: t.Iterable["SupportsAdd[int, t.Any]"]) -> float: ...
+
+
+def mean(collection):
+    """
+    Calculate arithmetic mean of each element in `collection`.
+
+    Args:
+        collection: Collection to process.
+
+    Returns:
+        Result of mean.
+
+    Example:
+
+        >>> mean([1, 2, 3, 4])
+        2.5
+
+    .. versionadded:: 2.1.0
+
+    .. versionchanged:: 4.0.0
+
+        - Removed ``average`` and ``avg`` aliases.
+        - Moved iteratee functionality to :func:`mean_by`.
+    """
+    return mean_by(collection)
+
+
+@t.overload
+def mean_by(
+    collection: t.Mapping[T, T2],
+    iteratee: t.Callable[[T2, T, t.Dict[T, T2]], "SupportsAdd[int, t.Any]"],
+) -> float: ...
+
+
+@t.overload
+def mean_by(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], "SupportsAdd[int, t.Any]"]
+) -> float: ...
+
+
+@t.overload
+def mean_by(
+    collection: t.Mapping[t.Any, T2], iteratee: t.Callable[[T2], "SupportsAdd[int, t.Any]"]
+) -> float: ...
+
+
+@t.overload
+def mean_by(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], "SupportsAdd[int, t.Any]"]
+) -> float: ...
+
+
+@t.overload
+def mean_by(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int], "SupportsAdd[int, t.Any]"]
+) -> float: ...
+
+
+@t.overload
+def mean_by(
+    collection: t.Iterable[T], iteratee: t.Callable[[T], "SupportsAdd[int, t.Any]"]
+) -> float: ...
+
+
+@t.overload
+def mean_by(
+    collection: t.Mapping[t.Any, "SupportsAdd[int, t.Any]"], iteratee: None = None
+) -> float: ...
+
+
+@t.overload
+def mean_by(collection: t.Iterable["SupportsAdd[int, t.Any]"], iteratee: None = None) -> float: ...
+
+
+def mean_by(collection, iteratee=None):
+    """
+    Calculate arithmetic mean of each element in `collection`. If iteratee is passed, each element
+    of `collection` is passed through an iteratee before the mean is computed.
+
+    Args:
+        collection: Collection to process.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Result of mean.
+
+    Example:
+
+        >>> mean_by([1, 2, 3, 4], lambda x: x**2)
+        7.5
+
+    .. versionadded:: 4.0.0
+    """
+    return sum_by(collection, iteratee) / len(collection)
+
+
+def ceil(x: NumberT, precision: int = 0) -> float:
+    """
+    Round number up to precision.
+
+    Args:
+        x: Number to round up.
+        precision: Rounding precision. Defaults to ``0``.
+
+    Returns:
+        Number rounded up.
+
+    Example:
+
+        >>> ceil(3.275) == 4.0
+        True
+        >>> ceil(3.215, 1) == 3.3
+        True
+        >>> ceil(6.004, 2) == 6.01
+        True
+
+    .. versionadded:: 3.3.0
+    """
+    return rounder(math.ceil, x, precision)
+
+
+NumT = t.TypeVar("NumT", int, float, "Decimal")
+NumT2 = t.TypeVar("NumT2", int, float, "Decimal")
+NumT3 = t.TypeVar("NumT3", int, float, "Decimal")
+
+
+def clamp(x: NumT, lower: NumT2, upper: t.Union[NumT3, None] = None) -> t.Union[NumT, NumT2, NumT3]:
+    """
+    Clamps number within the inclusive lower and upper bounds.
+
+    Args:
+        x: Number to clamp.
+        lower: Lower bound.
+        upper: Upper bound
+
+    Returns:
+        number
+
+    Example:
+
+        >>> clamp(-10, -5, 5)
+        -5
+        >>> clamp(10, -5, 5)
+        5
+        >>> clamp(10, 5)
+        5
+        >>> clamp(-10, 5)
+        -10
+
+    .. versionadded:: 4.0.0
+    """
+    if upper is None:
+        upper = lower  # type: ignore
+        lower = x  # type: ignore
+
+    if x < lower:
+        x = lower  # type: ignore
+    elif x > upper:  # type: ignore
+        x = upper  # type: ignore
+
+    return x
+
+
+def divide(dividend: t.Union[NumberT, None], divisor: t.Union[NumberT, None]) -> float:
+    """
+    Divide two numbers.
+
+    Args:
+        dividend: The first number in a division.
+        divisor: The second number in a division.
+
+    Returns:
+        Returns the quotient.
+
+    Example:
+
+        >>> divide(20, 5)
+        4.0
+        >>> divide(1.5, 3)
+        0.5
+        >>> divide(None, None)
+        1.0
+        >>> divide(5, None)
+        5.0
+
+    .. versionadded:: 4.0.0
+    """
+    return call_math_operator(dividend, divisor, operator.truediv, 1)
+
+
+def floor(x: NumberT, precision: int = 0) -> float:
+    """
+    Round number down to precision.
+
+    Args:
+        x: Number to round down.
+        precision: Rounding precision. Defaults to ``0``.
+
+    Returns:
+        Number rounded down.
+
+    Example:
+
+        >>> floor(3.75) == 3.0
+        True
+        >>> floor(3.215, 1) == 3.2
+        True
+        >>> floor(0.046, 2) == 0.04
+        True
+
+    .. versionadded:: 3.3.0
+    """
+    return rounder(math.floor, x, precision)
+
+
+@t.overload
+def max_(
+    collection: t.Mapping[t.Any, "SupportsRichComparisonT"], default: Unset = UNSET
+) -> "SupportsRichComparisonT": ...
+
+
+@t.overload
+def max_(
+    collection: t.Mapping[t.Any, "SupportsRichComparisonT"], default: T
+) -> t.Union["SupportsRichComparisonT", T]: ...
+
+
+@t.overload
+def max_(
+    collection: t.Iterable["SupportsRichComparisonT"], default: Unset = UNSET
+) -> "SupportsRichComparisonT": ...
+
+
+@t.overload
+def max_(
+    collection: t.Iterable["SupportsRichComparisonT"], default: T
+) -> t.Union["SupportsRichComparisonT", T]: ...
+
+
+def max_(collection, default=UNSET):
+    """
+    Retrieves the maximum value of a `collection`.
+
+    Args:
+        collection: Collection to iterate over.
+        default: Value to return if `collection` is empty.
+
+    Returns:
+        Maximum value.
+
+    Example:
+
+        >>> max_([1, 2, 3, 4])
+        4
+        >>> max_([], default=-1)
+        -1
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Moved iteratee iteratee support to :func:`max_by`.
+    """
+    return max_by(collection, default=default)
+
+
+@t.overload
+def max_by(
+    collection: t.Mapping[t.Any, "SupportsRichComparisonT"],
+    iteratee: None = None,
+    default: Unset = UNSET,
+) -> "SupportsRichComparisonT": ...
+
+
+@t.overload
+def max_by(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+    default: Unset = UNSET,
+) -> T2: ...
+
+
+@t.overload
+def max_by(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+    *,
+    default: T,
+) -> t.Union[T2, T]: ...
+
+
+@t.overload
+def max_by(
+    collection: t.Mapping[t.Any, "SupportsRichComparisonT"], iteratee: None = None, *, default: T
+) -> t.Union["SupportsRichComparisonT", T]: ...
+
+
+@t.overload
+def max_by(
+    collection: t.Iterable["SupportsRichComparisonT"], iteratee: None = None, default: Unset = UNSET
+) -> "SupportsRichComparisonT": ...
+
+
+@t.overload
+def max_by(
+    collection: t.Iterable[T2],
+    iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+    default: Unset = UNSET,
+) -> T2: ...
+
+
+@t.overload
+def max_by(
+    collection: t.Iterable[T2], iteratee: t.Callable[[T2], "SupportsRichComparisonT"], *, default: T
+) -> t.Union[T2, T]: ...
+
+
+@t.overload
+def max_by(
+    collection: t.Iterable["SupportsRichComparisonT"], iteratee: None = None, *, default: T
+) -> t.Union["SupportsRichComparisonT", T]: ...
+
+
+@t.overload
+def max_by(collection: t.Iterable[T], iteratee: IterateeObjT, default: Unset = UNSET) -> T: ...
+
+
+@t.overload
+def max_by(collection: t.Iterable[T], iteratee: IterateeObjT, default: T2) -> t.Union[T, T2]: ...
+
+
+def max_by(collection, iteratee=None, default=UNSET):
+    """
+    Retrieves the maximum value of a `collection`.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+        default: Value to return if `collection` is empty.
+
+    Returns:
+        Maximum value.
+
+    Example:
+
+        >>> max_by([1.0, 1.5, 1.8], math.floor)
+        1.0
+        >>> max_by([{"a": 1}, {"a": 2}, {"a": 3}], "a")
+        {'a': 3}
+        >>> max_by([], default=-1)
+        -1
+
+    .. versionadded:: 4.0.0
+    """
+    if isinstance(collection, dict):
+        collection = collection.values()
+
+    return max(iterator_with_default(collection, default), key=pyd.iteratee(iteratee))
+
+
+@t.overload
+def median(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T, t.Dict[T, T2]], NumberT]
+) -> t.Union[float, int]: ...
+
+
+@t.overload
+def median(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], NumberT]
+) -> t.Union[float, int]: ...
+
+
+@t.overload
+def median(
+    collection: t.Mapping[t.Any, T2], iteratee: t.Callable[[T2], NumberT]
+) -> t.Union[float, int]: ...
+
+
+@t.overload
+def median(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], NumberT]
+) -> t.Union[float, int]: ...
+
+
+@t.overload
+def median(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int], NumberT]
+) -> t.Union[float, int]: ...
+
+
+@t.overload
+def median(
+    collection: t.Iterable[T], iteratee: t.Callable[[T], NumberT]
+) -> t.Union[float, int]: ...
+
+
+@t.overload
+def median(collection: t.Iterable[NumberT], iteratee: None = None) -> t.Union[float, int]: ...
+
+
+def median(collection, iteratee=None):
+    """
+    Calculate median of each element in `collection`. If iteratee is passed, each element of
+    `collection` is passed through an iteratee before the median is computed.
+
+    Args:
+        collection: Collection to process.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Result of median.
+
+    Example:
+
+        >>> median([1, 2, 3, 4, 5])
+        3
+        >>> median([1, 2, 3, 4])
+        2.5
+
+    .. versionadded:: 2.1.0
+    """
+    length = len(collection)
+    middle = (length + 1) / 2
+    collection = sorted(ret[0] for ret in iteriteratee(collection, iteratee))
+
+    if pyd.is_odd(length):
+        result = collection[int(middle - 1)]
+    else:
+        left = int(middle - 1.5)
+        right = int(middle - 0.5)
+        result = (collection[left] + collection[right]) / 2
+
+    return result
+
+
+@t.overload
+def min_(
+    collection: t.Mapping[t.Any, "SupportsRichComparisonT"], default: Unset = UNSET
+) -> "SupportsRichComparisonT": ...
+
+
+@t.overload
+def min_(
+    collection: t.Mapping[t.Any, "SupportsRichComparisonT"], default: T
+) -> t.Union["SupportsRichComparisonT", T]: ...
+
+
+@t.overload
+def min_(
+    collection: t.Iterable["SupportsRichComparisonT"], default: Unset = UNSET
+) -> "SupportsRichComparisonT": ...
+
+
+@t.overload
+def min_(
+    collection: t.Iterable["SupportsRichComparisonT"], default: T
+) -> t.Union["SupportsRichComparisonT", T]: ...
+
+
+def min_(collection, default=UNSET):
+    """
+    Retrieves the minimum value of a `collection`.
+
+    Args:
+        collection: Collection to iterate over.
+        default: Value to return if `collection` is empty.
+
+    Returns:
+        Minimum value.
+
+    Example:
+
+        >>> min_([1, 2, 3, 4])
+        1
+        >>> min_([], default=100)
+        100
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Moved iteratee iteratee support to :func:`min_by`.
+    """
+    return min_by(collection, default=default)
+
+
+@t.overload
+def min_by(
+    collection: t.Mapping[t.Any, "SupportsRichComparisonT"],
+    iteratee: None = None,
+    default: Unset = UNSET,
+) -> "SupportsRichComparisonT": ...
+
+
+@t.overload
+def min_by(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+    default: Unset = UNSET,
+) -> T2: ...
+
+
+@t.overload
+def min_by(
+    collection: t.Mapping[t.Any, T2],
+    iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+    *,
+    default: T,
+) -> t.Union[T2, T]: ...
+
+
+@t.overload
+def min_by(
+    collection: t.Mapping[t.Any, "SupportsRichComparisonT"], iteratee: None = None, *, default: T
+) -> t.Union["SupportsRichComparisonT", T]: ...
+
+
+@t.overload
+def min_by(
+    collection: t.Iterable["SupportsRichComparisonT"], iteratee: None = None, default: Unset = UNSET
+) -> "SupportsRichComparisonT": ...
+
+
+@t.overload
+def min_by(
+    collection: t.Iterable[T2],
+    iteratee: t.Callable[[T2], "SupportsRichComparisonT"],
+    default: Unset = UNSET,
+) -> T2: ...
+
+
+@t.overload
+def min_by(
+    collection: t.Iterable[T2], iteratee: t.Callable[[T2], "SupportsRichComparisonT"], *, default: T
+) -> t.Union[T2, T]: ...
+
+
+@t.overload
+def min_by(
+    collection: t.Iterable["SupportsRichComparisonT"], iteratee: None = None, *, default: T
+) -> t.Union["SupportsRichComparisonT", T]: ...
+
+
+@t.overload
+def min_by(collection: t.Iterable[T], iteratee: IterateeObjT, default: Unset = UNSET) -> T: ...
+
+
+@t.overload
+def min_by(collection: t.Iterable[T], iteratee: IterateeObjT, default: T2) -> t.Union[T, T2]: ...
+
+
+def min_by(collection, iteratee=None, default=UNSET):
+    """
+    Retrieves the minimum value of a `collection`.
+
+    Args:
+        collection: Collection to iterate over.
+        iteratee: Iteratee applied per iteration.
+        default: Value to return if `collection` is empty.
+
+    Returns:
+        Minimum value.
+
+    Example:
+
+        >>> min_by([1.8, 1.5, 1.0], math.floor)
+        1.8
+        >>> min_by([{"a": 1}, {"a": 2}, {"a": 3}], "a")
+        {'a': 1}
+        >>> min_by([], default=100)
+        100
+
+    .. versionadded:: 4.0.0
+    """
+    if isinstance(collection, dict):
+        collection = collection.values()
+    return min(iterator_with_default(collection, default), key=pyd.iteratee(iteratee))
+
+
+def moving_mean(array: t.Sequence["SupportsAdd[int, t.Any]"], size: t.SupportsInt) -> t.List[float]:
+    """
+    Calculate moving mean of each element of `array`.
+
+    Args:
+        array: List to process.
+        size: Window size.
+
+    Returns:
+        Result of moving average.
+
+    Example:
+
+        >>> moving_mean(range(10), 1)
+        [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
+        >>> moving_mean(range(10), 5)
+        [2.0, 3.0, 4.0, 5.0, 6.0, 7.0]
+        >>> moving_mean(range(10), 10)
+        [4.5]
+
+    .. versionadded:: 2.1.0
+
+    .. versionchanged:: 4.0.0
+        Rename to ``moving_mean`` and remove ``moving_average`` and ``moving_avg`` aliases.
+    """
+    result = []
+    size = int(size)
+
+    for i in range(size - 1, len(array) + 1):
+        window = array[i - size : i]
+
+        if len(window) == size:
+            result.append(mean(window))
+
+    return result
+
+
+@t.overload
+def multiply(multiplier: SupportsMul[int, T2], multiplicand: None) -> T2: ...
+
+
+@t.overload
+def multiply(multiplier: None, multiplicand: SupportsMul[int, T2]) -> T2: ...
+
+
+@t.overload
+def multiply(multiplier: None, multiplicand: None) -> int: ...
+
+
+@t.overload
+def multiply(multiplier: SupportsMul[T, T2], multiplicand: T) -> T2: ...
+
+
+@t.overload
+def multiply(multiplier: T, multiplicand: SupportsMul[T, T2]) -> T2: ...
+
+
+def multiply(multiplier, multiplicand):
+    """
+    Multiply two numbers.
+
+    Args:
+        multiplier: The first number in a multiplication.
+        multiplicand: The second number in a multiplication.
+
+    Returns:
+        Returns the product.
+
+    Example:
+
+        >>> multiply(4, 5)
+        20
+        >>> multiply(10, 4)
+        40
+        >>> multiply(None, 10)
+        10
+        >>> multiply(None, None)
+        1
+
+    .. versionadded:: 4.0.0
+    """
+    return call_math_operator(multiplier, multiplicand, operator.mul, 1)
+
+
+@t.overload
+def power(x: int, n: int) -> t.Union[int, float]: ...
+
+
+@t.overload
+def power(x: float, n: t.Union[int, float]) -> float: ...
+
+
+@t.overload
+def power(x: t.List[int], n: int) -> t.List[t.Union[int, float]]: ...
+
+
+@t.overload
+def power(x: t.List[float], n: t.List[t.Union[int, float]]) -> t.List[float]: ...
+
+
+def power(x, n):
+    """
+    Calculate exponentiation of `x` raised to the `n` power.
+
+    Args:
+        x: Base number.
+        n: Exponent.
+
+    Returns:
+        Result of calculation.
+
+    Example:
+
+        >>> power(5, 2)
+        25
+        >>> power(12.5, 3)
+        1953.125
+
+    .. versionadded:: 2.1.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``pow_``.
+    """
+    if pyd.is_number(x):
+        result = pow(x, n)
+    elif pyd.is_list(x):
+        result = [pow(item, n) for item in x]
+    else:
+        result = None
+
+    return result
+
+
+@t.overload
+def round_(x: t.List[SupportsRound[NumberT]], precision: int = 0) -> t.List[float]: ...
+
+
+@t.overload
+def round_(x: SupportsRound[NumberT], precision: int = 0) -> float: ...
+
+
+def round_(x, precision=0):
+    """
+    Round number to precision.
+
+    Args:
+        x: Number to round.
+        precision: Rounding precision. Defaults to ``0``.
+
+    Returns:
+        Rounded number.
+
+    Example:
+
+        >>> round_(3.275) == 3.0
+        True
+        >>> round_(3.275, 1) == 3.3
+        True
+
+    .. versionadded:: 2.1.0
+
+    .. versionchanged:: 4.0.0
+        Remove alias ``curve``.
+    """
+    return rounder(round, x, precision)
+
+
+@t.overload
+def scale(array: t.Iterable["Decimal"], maximum: "Decimal") -> t.List["Decimal"]: ...
+
+
+@t.overload
+def scale(array: t.Iterable[NumberNoDecimalT], maximum: NumberNoDecimalT) -> t.List[float]: ...
+
+
+@t.overload
+def scale(array: t.Iterable[NumberT], maximum: int = 1) -> t.List[float]: ...
+
+
+def scale(array, maximum: NumberT = 1):
+    """
+    Scale list of value to a maximum number.
+
+    Args:
+        array: Numbers to scale.
+        maximum: Maximum scale value.
+
+    Returns:
+        Scaled numbers.
+
+    Example:
+
+        >>> scale([1, 2, 3, 4])
+        [0.25, 0.5, 0.75, 1.0]
+        >>> scale([1, 2, 3, 4], 1)
+        [0.25, 0.5, 0.75, 1.0]
+        >>> scale([1, 2, 3, 4], 4)
+        [1.0, 2.0, 3.0, 4.0]
+        >>> scale([1, 2, 3, 4], 2)
+        [0.5, 1.0, 1.5, 2.0]
+
+    .. versionadded:: 2.1.0
+    """
+    array_max = max(array)
+    factor = maximum / array_max
+    return [item * factor for item in array]
+
+
+@t.overload
+def slope(
+    point1: t.Union[t.Tuple["Decimal", "Decimal"], t.List["Decimal"]],
+    point2: t.Union[t.Tuple["Decimal", "Decimal"], t.List["Decimal"]],
+) -> "Decimal": ...
+
+
+@t.overload
+def slope(
+    point1: t.Union[t.Tuple[NumberNoDecimalT, NumberNoDecimalT], t.List[NumberNoDecimalT]],
+    point2: t.Union[t.Tuple[NumberNoDecimalT, NumberNoDecimalT], t.List[NumberNoDecimalT]],
+) -> float: ...
+
+
+def slope(point1, point2):
+    """
+    Calculate the slope between two points.
+
+    Args:
+        point1: X and Y coordinates of first point.
+        point2: X and Y cooredinates of second point.
+
+    Returns:
+        Calculated slope.
+
+    Example:
+
+        >>> slope((1, 2), (4, 8))
+        2.0
+
+    .. versionadded:: 2.1.0
+    """
+    x1, y1 = point1[0], point1[1]
+    x2, y2 = point2[0], point2[1]
+
+    if x1 == x2:
+        result = INFINITY
+    else:
+        result = (y2 - y1) / (x2 - x1)
+
+    return result
+
+
+def std_deviation(array: t.List[NumberT]) -> float:
+    """
+    Calculate standard deviation of list of numbers.
+
+    Args:
+        array: List to process.
+
+    Returns:
+        Calculated standard deviation.
+
+    Example:
+
+        >>> round(std_deviation([1, 18, 20, 4]), 2) == 8.35
+        True
+
+    .. versionadded:: 2.1.0
+
+    .. versionchanged:: 4.0.0
+        Remove alias ``sigma``.
+    """
+    return math.sqrt(variance(array))
+
+
+@t.overload
+def subtract(minuend: "SupportsSub[T, T2]", subtrahend: T) -> T2: ...
+
+
+@t.overload
+def subtract(minuend: T, subtrahend: "SupportsSub[T, T2]") -> T2: ...
+
+
+def subtract(minuend, subtrahend):
+    """
+    Subtracts two numbers.
+
+    Args:
+        minuend: Value passed in by the user.
+        subtrahend: Value passed in by the user.
+
+    Returns:
+        Result of the difference from the given values.
+
+    Example:
+
+        >>> subtract(10, 5)
+        5
+        >>> subtract(-10, 4)
+        -14
+        >>> subtract(2, 0.5)
+        1.5
+
+    .. versionadded:: 4.0.0
+    """
+    return call_math_operator(minuend, subtrahend, operator.sub, 0)
+
+
+def transpose(array: t.Iterable[t.Iterable[T]]) -> t.List[t.List[T]]:
+    """
+    Transpose the elements of `array`.
+
+    Args:
+        array: List to process.
+
+    Returns:
+        Transposed list.
+
+    Example:
+
+        >>> transpose([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
+        [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
+
+    .. versionadded:: 2.1.0
+    """
+    trans: t.List[t.List[T]] = []
+
+    for y, row in iterator(array):
+        for x, col in iterator(row):
+            trans = pyd.set_(trans, [x, y], col)
+
+    return trans
+
+
+@t.overload
+def variance(array: t.Mapping[t.Any, "SupportsAdd[int, t.Any]"]) -> float: ...
+
+
+@t.overload
+def variance(array: t.Iterable["SupportsAdd[int, t.Any]"]) -> float: ...
+
+
+def variance(array):
+    """
+    Calculate the variance of the elements in `array`.
+
+    Args:
+        array: List to process.
+
+    Returns:
+        Calculated variance.
+
+    Example:
+
+        >>> variance([1, 18, 20, 4])
+        69.6875
+
+    .. versionadded:: 2.1.0
+    """
+    avg = mean(array)
+
+    def var(x):
+        return power(x - avg, 2)
+
+    return pyd._(array).map_(var).mean().value()
+
+
+@t.overload
+def zscore(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T, t.Dict[T, T2]], NumberT]
+) -> t.List[float]: ...
+
+
+@t.overload
+def zscore(
+    collection: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], NumberT]
+) -> t.List[float]: ...
+
+
+@t.overload
+def zscore(
+    collection: t.Mapping[t.Any, T2], iteratee: t.Callable[[T2], NumberT]
+) -> t.List[float]: ...
+
+
+@t.overload
+def zscore(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], NumberT]
+) -> t.List[float]: ...
+
+
+@t.overload
+def zscore(collection: t.Iterable[T], iteratee: t.Callable[[T, int], NumberT]) -> t.List[float]: ...
+
+
+@t.overload
+def zscore(collection: t.Iterable[T], iteratee: t.Callable[[T], NumberT]) -> t.List[float]: ...
+
+
+@t.overload
+def zscore(collection: t.Iterable[NumberT], iteratee: None = None) -> t.List[float]: ...
+
+
+def zscore(collection, iteratee=None):
+    """
+    Calculate the standard score assuming normal distribution. If iteratee is passed, each element
+    of `collection` is passed through an iteratee before the standard score is computed.
+
+    Args:
+        collection: Collection to process.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Calculated standard score.
+
+    Example:
+
+        >>> results = zscore([1, 2, 3])
+
+        # [-1.224744871391589, 0.0, 1.224744871391589]
+
+    .. versionadded:: 2.1.0
+    """
+    array = pyd.map_(collection, iteratee)
+    avg = mean(array)
+    sig = std_deviation(array)
+
+    return [(item - avg) / sig for item in array]
+
+
+#
+# Utility methods not a part of the main API
+#
+
+
+def call_math_operator(value1, value2, op, default):
+    """Return the result of the math operation on the given values."""
+    if value1 is None:
+        value1 = default
+
+    if value2 is None:
+        value2 = default
+
+    if not pyd.is_number(value1):
+        try:
+            value1 = float(value1)
+        except Exception:
+            pass
+
+    if not pyd.is_number(value2):
+        try:
+            value2 = float(value2)
+        except Exception:
+            pass
+
+    return op(value1, value2)
+
+
+def rounder(func, x, precision):
+    precision = pow(10, precision)
+
+    def rounder_func(item):
+        return func(item * precision) / precision
+
+    result = None
+
+    if pyd.is_number(x):
+        result = rounder_func(x)
+    elif pyd.is_iterable(x):
+        try:
+            result = [rounder_func(item) for item in x]
+        except TypeError:
+            pass
+
+    return result
diff --git a/.venv/lib/python3.12/site-packages/pydash/objects.py b/.venv/lib/python3.12/site-packages/pydash/objects.py
new file mode 100644
index 00000000..442cf722
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/objects.py
@@ -0,0 +1,2633 @@
+"""
+Functions that operate on lists, dicts, and other objects.
+
+.. versionadded:: 1.0.0
+"""
+
+from __future__ import annotations
+
+import copy
+from functools import partial
+import math
+import re
+import typing as t
+
+import pydash as pyd
+
+from .helpers import UNSET, Unset, base_get, base_set, callit, getargcount, iterator, iteriteratee
+from .types import IterateeObjT, PathT
+from .utilities import PathToken, to_path, to_path_tokens
+
+
+if t.TYPE_CHECKING:
+    from _typeshed import SupportsRichComparisonT  # pragma: no cover
+
+__all__ = (
+    "apply",
+    "apply_catch",
+    "apply_if",
+    "apply_if_not_none",
+    "assign",
+    "assign_with",
+    "callables",
+    "clone",
+    "clone_deep",
+    "clone_deep_with",
+    "clone_with",
+    "defaults",
+    "defaults_deep",
+    "find_key",
+    "find_last_key",
+    "for_in",
+    "for_in_right",
+    "get",
+    "has",
+    "invert",
+    "invert_by",
+    "invoke",
+    "keys",
+    "map_keys",
+    "map_values",
+    "map_values_deep",
+    "merge",
+    "merge_with",
+    "omit",
+    "omit_by",
+    "parse_int",
+    "pick",
+    "pick_by",
+    "rename_keys",
+    "set_",
+    "set_with",
+    "to_boolean",
+    "to_dict",
+    "to_integer",
+    "to_list",
+    "to_number",
+    "to_pairs",
+    "to_string",
+    "transform",
+    "unset",
+    "update",
+    "update_with",
+    "values",
+)
+
+T = t.TypeVar("T")
+T2 = t.TypeVar("T2")
+T3 = t.TypeVar("T3")
+T4 = t.TypeVar("T4")
+T5 = t.TypeVar("T5")
+
+
+@t.overload
+def assign(
+    obj: t.Mapping[T, T2], *sources: t.Mapping[T3, T4]
+) -> t.Dict[t.Union[T, T3], t.Union[T2, T4]]: ...
+
+
+@t.overload
+def assign(
+    obj: t.Union[t.Tuple[T, ...], t.List[T]], *sources: t.Mapping[int, T2]
+) -> t.List[t.Union[T, T2]]: ...
+
+
+def assign(obj, *sources) -> t.Union[t.List[t.Any], t.Dict[t.Any, t.Any]]:
+    """
+    Assigns properties of source object(s) to the destination object.
+
+    Args:
+        obj: Destination object whose properties will be modified.
+        sources: Source objects to assign to `obj`.
+
+    Returns:
+        Modified `obj`.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> obj = {}
+        >>> obj2 = assign(obj, {"a": 1}, {"b": 2}, {"c": 3})
+        >>> obj == {"a": 1, "b": 2, "c": 3}
+        True
+        >>> obj is obj2
+        True
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 2.3.2
+        Apply :func:`clone_deep` to each `source` before assigning to `obj`.
+
+    .. versionchanged:: 3.0.0
+        Allow iteratees to accept partial arguments.
+
+    .. versionchanged:: 3.4.4
+        Shallow copy each `source` instead of deep copying.
+
+    .. versionchanged:: 4.0.0
+
+        - Moved `iteratee` argument to :func:`assign_with`.
+        - Removed alias ``extend``.
+    """
+    return assign_with(obj, *sources)  # type: ignore
+
+
+@t.overload
+def assign_with(
+    obj: t.Mapping[T, T2],
+    *sources: t.Mapping[T3, t.Any],
+    customizer: t.Callable[[t.Union[T2, None]], T5],
+) -> t.Dict[t.Union[T, T3], t.Union[T2, T5]]: ...
+
+
+@t.overload
+def assign_with(
+    obj: t.Mapping[T, T2],
+    *sources: t.Mapping[T3, T4],
+    customizer: t.Callable[[t.Union[T2, None], T4], T5],
+) -> t.Dict[t.Union[T, T3], t.Union[T2, T5]]: ...
+
+
+@t.overload
+def assign_with(
+    obj: t.Mapping[T, T2],
+    *sources: t.Mapping[T3, T4],
+    customizer: t.Callable[[t.Union[T2, None], T4, T3], T5],
+) -> t.Dict[t.Union[T, T3], t.Union[T2, T5]]: ...
+
+
+@t.overload
+def assign_with(
+    obj: t.Mapping[T, T2],
+    *sources: t.Mapping[T3, T4],
+    customizer: t.Callable[[t.Union[T2, None], T4, T3, t.Dict[T, T2]], T5],
+) -> t.Dict[t.Union[T, T3], t.Union[T2, T5]]: ...
+
+
+@t.overload
+def assign_with(
+    obj: t.Mapping[T, T2],
+    *sources: t.Mapping[T3, T4],
+    customizer: t.Callable[[t.Union[T2, None], T4, T3, t.Dict[T, T2], t.Dict[T3, T4]], T5],
+) -> t.Dict[t.Union[T, T3], t.Union[T2, T5]]: ...
+
+
+@t.overload
+def assign_with(
+    obj: t.Mapping[T, T2], *sources: t.Mapping[T3, T4], customizer: None = None
+) -> t.Dict[t.Union[T, T3], t.Union[T2, T4]]: ...
+
+
+def assign_with(obj, *sources, customizer=None):
+    """
+    This method is like :func:`assign` except that it accepts customizer which is invoked to produce
+    the assigned values. If customizer returns ``None``, assignment is handled by the method
+    instead. The customizer is invoked with five arguments: ``(obj_value, src_value, key, obj,
+    source)``.
+
+    Args:
+        obj: Destination object whose properties will be modified.
+        sources: Source objects to assign to `obj`.
+
+    Keyword Args:
+        customizer: Customizer applied per iteration.
+
+    Returns:
+        Modified `obj`.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> customizer = lambda o, s: s if o is None else o
+        >>> results = assign_with({"a": 1}, {"b": 2}, {"a": 3}, customizer)
+        >>> results == {"a": 1, "b": 2}
+        True
+
+    .. versionadded:: 4.0.0
+    """
+    sources = list(sources)
+
+    if customizer is None and callable(sources[-1]):
+        customizer = sources.pop()
+
+    if customizer is not None:
+        argcount = getargcount(customizer, maxargs=5)
+    else:
+        argcount = None
+
+    for source in sources:
+        source = source.copy()
+
+        for key, value in source.items():
+            if customizer:
+                val = callit(customizer, obj.get(key), value, key, obj, source, argcount=argcount)
+                if val is not None:
+                    value = val
+
+            obj[key] = value
+
+    return obj
+
+
+@t.overload
+def callables(
+    obj: t.Mapping["SupportsRichComparisonT", t.Any],
+) -> t.List["SupportsRichComparisonT"]: ...
+
+
+@t.overload
+def callables(obj: t.Iterable[T]) -> t.List[T]: ...
+
+
+def callables(obj):
+    """
+    Creates a sorted list of keys of an object that are callable.
+
+    Args:
+        obj: Object to inspect.
+
+    Returns:
+        All keys whose values are callable.
+
+    Example:
+
+        >>> callables({"a": 1, "b": lambda: 2, "c": lambda: 3})
+        ['b', 'c']
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 2.0.0
+        Renamed ``functions`` to ``callables``.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``methods``.
+    """
+    return sorted(key for key, value in iterator(obj) if callable(value))
+
+
+def clone(value: T) -> T:
+    """
+    Creates a clone of `value`.
+
+    Args:
+        value: Object to clone.
+
+    Example:
+
+        >>> x = {"a": 1, "b": 2, "c": {"d": 3}}
+        >>> y = clone(x)
+        >>> y == y
+        True
+        >>> x is y
+        False
+        >>> x["c"] is y["c"]
+        True
+
+    Returns:
+        Cloned object.
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Moved 'iteratee' parameter to :func:`clone_with`.
+    """
+    return base_clone(value)
+
+
+@t.overload
+def clone_with(
+    value: t.Mapping[T, T2], customizer: t.Callable[[T2, T, t.Mapping[T, T2]], T3]
+) -> t.Dict[T, t.Union[T2, T3]]: ...
+
+
+@t.overload
+def clone_with(
+    value: t.Mapping[T, T2], customizer: t.Callable[[T2, T], T3]
+) -> t.Dict[T, t.Union[T2, T3]]: ...
+
+
+@t.overload
+def clone_with(
+    value: t.Mapping[T, T2], customizer: t.Callable[[T2], T3]
+) -> t.Dict[T, t.Union[T2, T3]]: ...
+
+
+@t.overload
+def clone_with(
+    value: t.List[T], customizer: t.Callable[[T, int, t.List[T]], T2]
+) -> t.List[t.Union[T, T2]]: ...
+
+
+@t.overload
+def clone_with(
+    value: t.List[T], customizer: t.Callable[[T, int], T2]
+) -> t.List[t.Union[T, T2]]: ...
+
+
+@t.overload
+def clone_with(value: t.List[T], customizer: t.Callable[[T], T2]) -> t.List[t.Union[T, T2]]: ...
+
+
+@t.overload
+def clone_with(value: T, customizer: None = None) -> T: ...
+
+
+@t.overload
+def clone_with(value: t.Any, customizer: t.Callable[..., t.Any]) -> t.Any: ...
+
+
+def clone_with(value, customizer=None):
+    """
+    This method is like :func:`clone` except that it accepts customizer which is invoked to produce
+    the cloned value. If customizer returns ``None``, cloning is handled by the method instead. The
+    customizer is invoked with up to three arguments: ``(value, index|key, object)``.
+
+    Args:
+        value: Object to clone.
+        customizer: Function to customize cloning.
+
+    Returns:
+        Cloned object.
+
+    Example:
+
+        >>> x = {"a": 1, "b": 2, "c": {"d": 3}}
+        >>> cbk = lambda v, k: v + 2 if isinstance(v, int) and k else None
+        >>> y = clone_with(x, cbk)
+        >>> y == {"a": 3, "b": 4, "c": {"d": 3}}
+        True
+    """
+    return base_clone(value, customizer=customizer)
+
+
+def clone_deep(value: T) -> T:
+    """
+    Creates a deep clone of `value`. If an iteratee is provided it will be executed to produce the
+    cloned values.
+
+    Args:
+        value: Object to clone.
+
+    Returns:
+        Cloned object.
+
+    Example:
+
+        >>> x = {"a": 1, "b": 2, "c": {"d": 3}}
+        >>> y = clone_deep(x)
+        >>> y == y
+        True
+        >>> x is y
+        False
+        >>> x["c"] is y["c"]
+        False
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Moved 'iteratee' parameter to :func:`clone_deep_with`.
+    """
+    return base_clone(value, is_deep=True)
+
+
+@t.overload
+def clone_deep_with(
+    value: t.Mapping[T, T2], customizer: t.Callable[[T2, T, t.Mapping[T, T2]], T3]
+) -> t.Dict[T, t.Union[T2, T3]]: ...
+
+
+@t.overload
+def clone_deep_with(
+    value: t.Mapping[T, T2], customizer: t.Callable[[T2, T], T3]
+) -> t.Dict[T, t.Union[T2, T3]]: ...
+
+
+@t.overload
+def clone_deep_with(
+    value: t.Mapping[T, T2], customizer: t.Callable[[T2], T3]
+) -> t.Dict[T, t.Union[T2, T3]]: ...
+
+
+@t.overload
+def clone_deep_with(
+    value: t.List[T], customizer: t.Callable[[T, int, t.List[T]], T2]
+) -> t.List[t.Union[T, T2]]: ...
+
+
+@t.overload
+def clone_deep_with(
+    value: t.List[T], customizer: t.Callable[[T, int], T2]
+) -> t.List[t.Union[T, T2]]: ...
+
+
+@t.overload
+def clone_deep_with(
+    value: t.List[T], customizer: t.Callable[[T], T2]
+) -> t.List[t.Union[T, T2]]: ...
+
+
+@t.overload
+def clone_deep_with(value: T, customizer: None = None) -> T: ...
+
+
+@t.overload
+def clone_deep_with(value: t.Any, customizer: t.Callable[..., t.Any]) -> t.Any: ...
+
+
+def clone_deep_with(value, customizer=None):
+    """
+    This method is like :func:`clone_with` except that it recursively clones `value`.
+
+    Args:
+        value: Object to clone.
+        customizer: Function to customize cloning.
+
+    Returns:
+        Cloned object.
+    """
+    return base_clone(value, is_deep=True, customizer=customizer)
+
+
+def defaults(
+    obj: t.Dict[T, T2], *sources: t.Dict[T3, T4]
+) -> t.Dict[t.Union[T, T3], t.Union[T2, T4]]:
+    """
+    Assigns properties of source object(s) to the destination object for all destination properties
+    that resolve to undefined.
+
+    Args:
+        obj: Destination object whose properties will be modified.
+        sources: Source objects to assign to `obj`.
+
+    Returns:
+        Modified `obj`.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> obj = {"a": 1}
+        >>> obj2 = defaults(obj, {"b": 2}, {"c": 3}, {"a": 4})
+        >>> obj is obj2
+        True
+        >>> obj == {"a": 1, "b": 2, "c": 3}
+        True
+
+    .. versionadded:: 1.0.0
+    """
+    for source in sources:
+        for key, value in source.items():
+            obj.setdefault(key, value)  # type: ignore
+
+    return obj  # type: ignore
+
+
+def defaults_deep(
+    obj: t.Dict[T, T2], *sources: t.Dict[T3, T4]
+) -> t.Dict[t.Union[T, T3], t.Union[T2, T4]]:
+    """
+    This method is like :func:`defaults` except that it recursively assigns default properties.
+
+    Args:
+        obj: Destination object whose properties will be modified.
+        sources: Source objects to assign to `obj`.
+
+    Returns:
+        Modified `obj`.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> obj = {"a": {"b": 1}}
+        >>> obj2 = defaults_deep(obj, {"a": {"b": 2, "c": 3}})
+        >>> obj is obj2
+        True
+        >>> obj == {"a": {"b": 1, "c": 3}}
+        True
+
+    .. versionadded:: 3.3.0
+    """
+
+    def setter(obj, key, value):
+        if hasattr(obj, "setdefault"):
+            obj.setdefault(key, value)
+
+    return merge_with(obj, *sources, _setter=setter)
+
+
+@t.overload
+def find_key(
+    obj: t.Mapping[T, T2], predicate: t.Callable[[T2, T, t.Dict[T, T2]], t.Any]
+) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find_key(obj: t.Mapping[T, T2], predicate: t.Callable[[T2, T], t.Any]) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find_key(obj: t.Mapping[T, T2], predicate: t.Callable[[T2], t.Any]) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find_key(obj: t.Mapping[T, t.Any], predicate: None = None) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find_key(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], t.Any]
+) -> t.Union[int, None]: ...
+
+
+@t.overload
+def find_key(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int], t.Any]
+) -> t.Union[int, None]: ...
+
+
+@t.overload
+def find_key(collection: t.Iterable[T], iteratee: t.Callable[[T], t.Any]) -> t.Union[int, None]: ...
+
+
+@t.overload
+def find_key(collection: t.Iterable[t.Any], iteratee: None = None) -> t.Union[int, None]: ...
+
+
+def find_key(obj, predicate=None):
+    """
+    This method is like :func:`pydash.arrays.find_index` except that it returns the key of the first
+    element that passes the predicate check, instead of the element itself.
+
+    Args:
+        obj: Object to search.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        Found key or ``None``.
+
+    Example:
+
+        >>> find_key({"a": 1, "b": 2, "c": 3}, lambda x: x == 1)
+        'a'
+        >>> find_key([1, 2, 3, 4], lambda x: x == 1)
+        0
+
+    .. versionadded:: 1.0.0
+    """
+    for result, _, key, _ in iteriteratee(obj, predicate):
+        if result:
+            return key
+
+
+@t.overload
+def find_last_key(
+    obj: t.Mapping[T, T2], predicate: t.Callable[[T2, T, t.Dict[T, T2]], t.Any]
+) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find_last_key(
+    obj: t.Mapping[T, T2], predicate: t.Callable[[T2, T], t.Any]
+) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find_last_key(
+    obj: t.Mapping[T, T2], predicate: t.Callable[[T2], t.Any]
+) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find_last_key(obj: t.Mapping[T, t.Any], predicate: None = None) -> t.Union[T, None]: ...
+
+
+@t.overload
+def find_last_key(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], t.Any]
+) -> t.Union[int, None]: ...
+
+
+@t.overload
+def find_last_key(
+    collection: t.Iterable[T], iteratee: t.Callable[[T, int], t.Any]
+) -> t.Union[int, None]: ...
+
+
+@t.overload
+def find_last_key(
+    collection: t.Iterable[T], iteratee: t.Callable[[T], t.Any]
+) -> t.Union[int, None]: ...
+
+
+@t.overload
+def find_last_key(collection: t.Iterable[t.Any], iteratee: None = None) -> t.Union[int, None]: ...
+
+
+def find_last_key(obj, predicate=None):
+    """
+    This method is like :func:`find_key` except that it iterates over elements of a collection in
+    the opposite order.
+
+    Args:
+        obj: Object to search.
+        predicate: Predicate applied per iteration.
+
+    Returns:
+        Found key or ``None``.
+
+    Example:
+
+        >>> find_last_key({"a": 1, "b": 2, "c": 3}, lambda x: x == 1)
+        'a'
+        >>> find_last_key([1, 2, 3, 1], lambda x: x == 1)
+        3
+
+    .. versionchanged:: 4.0.0
+        Made into its own function (instead of an alias of ``find_key``) with
+        proper reverse find implementation.
+    """
+    reversed_obj = reversed(list(iteriteratee(obj, predicate)))
+    for result, _, key, _ in reversed_obj:
+        if result:
+            return key
+
+
+@t.overload
+def for_in(
+    obj: t.Mapping[T, T2], iteratee: t.Callable[[T2, T, t.Dict[T, T2]], t.Any]
+) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_in(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], t.Any]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_in(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2], t.Any]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_in(obj: t.Mapping[T, T2], iteratee: None = None) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_in(obj: t.Sequence[T], iteratee: t.Callable[[T, int, t.List[T]], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def for_in(obj: t.Sequence[T], iteratee: t.Callable[[T, int], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def for_in(obj: t.Sequence[T], iteratee: t.Callable[[T], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def for_in(obj: t.Sequence[T], iteratee: None = None) -> t.List[T]: ...
+
+
+def for_in(obj, iteratee=None):
+    """
+    Iterates over own and inherited enumerable properties of `obj`, executing `iteratee` for each
+    property.
+
+    Args:
+        obj: Object to process.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        `obj`.
+
+    Example:
+
+        >>> obj = {}
+        >>> def cb(v, k):
+        ...     obj[k] = v
+        >>> results = for_in({"a": 1, "b": 2, "c": 3}, cb)
+        >>> results == {"a": 1, "b": 2, "c": 3}
+        True
+        >>> obj == {"a": 1, "b": 2, "c": 3}
+        True
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``for_own``.
+    """
+    walk = (None for ret, _, _, _ in iteriteratee(obj, iteratee) if ret is False)
+    next(walk, None)
+    return obj
+
+
+@t.overload
+def for_in_right(
+    obj: t.Mapping[T, T2], iteratee: t.Callable[[T2, T, t.Dict[T, T2]], t.Any]
+) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_in_right(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], t.Any]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_in_right(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2], t.Any]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_in_right(obj: t.Mapping[T, T2], iteratee: None = None) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def for_in_right(
+    obj: t.Sequence[T], iteratee: t.Callable[[T, int, t.List[T]], t.Any]
+) -> t.List[T]: ...
+
+
+@t.overload
+def for_in_right(obj: t.Sequence[T], iteratee: t.Callable[[T, int], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def for_in_right(obj: t.Sequence[T], iteratee: t.Callable[[T], t.Any]) -> t.List[T]: ...
+
+
+@t.overload
+def for_in_right(obj: t.Sequence[T], iteratee: None = None) -> t.List[T]: ...
+
+
+def for_in_right(obj, iteratee=None):
+    """
+    This function is like :func:`for_in` except it iterates over the properties in reverse order.
+
+    Args:
+        obj: Object to process.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        `obj`.
+
+    Example:
+
+        >>> data = {"product": 1}
+        >>> def cb(v):
+        ...     data["product"] *= v
+        >>> for_in_right([1, 2, 3, 4], cb)
+        [1, 2, 3, 4]
+        >>> data["product"] == 24
+        True
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``for_own_right``.
+    """
+    walk = (None for ret, _, _, _ in iteriteratee(obj, iteratee, reverse=True) if ret is False)
+    next(walk, None)
+    return obj
+
+
+@t.overload
+def get(obj: t.List[T], path: int, default: T2) -> t.Union[T, T2]: ...
+
+
+@t.overload
+def get(obj: t.List[T], path: int, default: None = None) -> t.Union[T, None]: ...
+
+
+@t.overload
+def get(obj: t.Any, path: PathT, default: t.Any = None) -> t.Any: ...
+
+
+def get(obj: t.Any, path: PathT, default: t.Any = None) -> t.Any:
+    """
+    Get the value at any depth of a nested object based on the path described by `path`. If path
+    doesn't exist, `default` is returned.
+
+    Args:
+        obj: Object to process.
+        path: List or ``.`` delimited string of path describing path.
+        default: Default value to return if path doesn't exist. Defaults to ``None``.
+
+    Returns:
+        Value of `obj` at path.
+
+    Example:
+
+        >>> get({}, "a.b.c") is None
+        True
+        >>> get({"a": {"b": {"c": [1, 2, 3, 4]}}}, "a.b.c[1]")
+        2
+        >>> get({"a": {"b": {"c": [1, 2, 3, 4]}}}, "a.b.c.1")
+        2
+        >>> get({"a": {"b": [0, {"c": [1, 2]}]}}, "a.b.1.c.1")
+        2
+        >>> get({"a": {"b": [0, {"c": [1, 2]}]}}, ["a", "b", 1, "c", 1])
+        2
+        >>> get({"a": {"b": [0, {"c": [1, 2]}]}}, "a.b.1.c.2") is None
+        True
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 2.2.0
+        Support escaping "." delimiter in single string path key.
+
+    .. versionchanged:: 3.3.0
+
+        - Added :func:`get` as main definition and :func:`get_path` as alias.
+        - Made :func:`deep_get` an alias.
+
+    .. versionchanged:: 3.4.7
+        Fixed bug where an iterable default was iterated over instead of being returned when an
+        object path wasn't found.
+
+    .. versionchanged:: 4.0.0
+
+        - Support attribute access on `obj` if item access fails.
+        - Removed aliases ``get_path`` and ``deep_get``.
+
+    .. versionchanged:: 4.7.6
+        Fixed bug where getattr is used on Mappings and Sequence in Python 3.5+
+    """
+    if default is UNSET:
+        # When NoValue given for default, then this method will raise if path is not present in obj.
+        sentinel = default
+    else:
+        # When a returnable default is given, use a sentinel value to detect when base_get() returns
+        # a default value for a missing path, so we can exit early from the loop and not mistakenly
+        # iterate over the default.
+        sentinel = object()
+
+    for key in to_path(path):
+        obj = base_get(obj, key, default=sentinel)
+
+        if obj is sentinel:
+            # Path doesn't exist so set return obj to the default.
+            obj = default
+            break
+
+    return obj
+
+
+def has(obj: t.Any, path: PathT) -> bool:
+    """
+    Checks if `path` exists as a key of `obj`.
+
+    Args:
+        obj: Object to test.
+        path: Path to test for. Can be a list of nested keys or a ``.`` delimited string of
+            path describing the path.
+
+    Returns:
+        Whether `obj` has `path`.
+
+    Example:
+
+        >>> has([1, 2, 3], 1)
+        True
+        >>> has({"a": 1, "b": 2}, "b")
+        True
+        >>> has({"a": 1, "b": 2}, "c")
+        False
+        >>> has({"a": {"b": [0, {"c": [1, 2]}]}}, "a.b.1.c.1")
+        True
+        >>> has({"a": {"b": [0, {"c": [1, 2]}]}}, "a.b.1.c.2")
+        False
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 3.0.0
+        Return ``False`` on ``ValueError`` when checking path.
+
+    .. versionchanged:: 3.3.0
+
+        - Added :func:`deep_has` as alias.
+        - Added :func:`has_path` as alias.
+
+    .. versionchanged:: 4.0.0
+        Removed aliases ``deep_has`` and ``has_path``.
+    """
+    try:
+        get(obj, path, default=UNSET)
+        exists = True
+    except (KeyError, IndexError, TypeError, ValueError):
+        exists = False
+
+    return exists
+
+
+@t.overload
+def invert(obj: t.Mapping[T, T2]) -> t.Dict[T2, T]: ...
+
+
+@t.overload
+def invert(obj: t.Union[t.Iterator[T], t.Sequence[T]]) -> t.Dict[T, int]: ...
+
+
+def invert(obj):
+    """
+    Creates an object composed of the inverted keys and values of the given object.
+
+    Args:
+        obj: Dict to invert.
+
+    Returns:
+        Inverted dict.
+
+    Example:
+
+        >>> results = invert({"a": 1, "b": 2, "c": 3})
+        >>> results == {1: "a", 2: "b", 3: "c"}
+        True
+
+    Note:
+        Assumes `obj` values are hashable as ``dict`` keys.
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 2.0.0
+        Added ``multivalue`` argument.
+
+    .. versionchanged:: 4.0.0
+        Moved ``multivalue=True`` functionality to :func:`invert_by`.
+    """
+    return {value: key for key, value in iterator(obj)}
+
+
+@t.overload
+def invert_by(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2], T3]) -> t.Dict[T3, t.List[T]]: ...
+
+
+@t.overload
+def invert_by(obj: t.Mapping[T, T2], iteratee: None = None) -> t.Dict[T2, t.List[T]]: ...
+
+
+@t.overload
+def invert_by(
+    obj: t.Union[t.Iterator[T], t.Sequence[T]], iteratee: t.Callable[[T], T2]
+) -> t.Dict[T2, t.List[int]]: ...
+
+
+@t.overload
+def invert_by(
+    obj: t.Union[t.Iterator[T], t.Sequence[T]], iteratee: None = None
+) -> t.Dict[T, t.List[int]]: ...
+
+
+def invert_by(obj, iteratee=None):
+    """
+    This method is like :func:`invert` except that the inverted object is generated from the results
+    of running each element of object through iteratee. The corresponding inverted value of each
+    inverted key is a list of keys responsible for generating the inverted value. The iteratee is
+    invoked with one argument: ``(value)``.
+
+    Args:
+        obj: Object to invert.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Inverted dict.
+
+    Example:
+
+        >>> obj = {"a": 1, "b": 2, "c": 1}
+        >>> results = invert_by(obj)  # {1: ['a', 'c'], 2: ['b']}
+        >>> set(results[1]) == set(["a", "c"])
+        True
+        >>> set(results[2]) == set(["b"])
+        True
+        >>> results2 = invert_by(obj, lambda value: "group" + str(value))
+        >>> results2["group1"] == results[1]
+        True
+        >>> results2["group2"] == results[2]
+        True
+
+    Note:
+        Assumes `obj` values are hashable as ``dict`` keys.
+
+    .. versionadded:: 4.0.0
+    """
+    callback = pyd.iteratee(iteratee)
+    result = {}
+
+    for key, value in iterator(obj):
+        result.setdefault(callback(value), []).append(key)
+
+    return result
+
+
+def invoke(obj: t.Any, path: PathT, *args: t.Any, **kwargs: t.Any) -> t.Any:
+    """
+    Invokes the method at path of object.
+
+    Args:
+        obj: The object to query.
+        path: The path of the method to invoke.
+        args: Arguments to pass to method call.
+        kwargs: Keyword arguments to pass to method call.
+
+    Returns:
+        Result of the invoked method.
+
+    Example:
+
+        >>> obj = {"a": [{"b": {"c": [1, 2, 3, 4]}}]}
+        >>> invoke(obj, "a[0].b.c.pop", 1)
+        2
+        >>> obj
+        {'a': [{'b': {'c': [1, 3, 4]}}]}
+
+    .. versionadded:: 1.0.0
+    """
+    paths = to_path(path)
+    target_path = pyd.initial(paths)
+    method_name = pyd.last(paths)
+
+    try:
+        # potential error is caught
+        method = getattr(get(obj, target_path), method_name)  # type: ignore
+    except AttributeError:
+        ret = None
+    else:
+        ret = method(*args, **kwargs)
+
+    return ret
+
+
+@t.overload
+def keys(obj: t.Iterable[T]) -> t.List[T]: ...
+
+
+@t.overload
+def keys(obj: t.Any) -> t.List[t.Any]: ...
+
+
+def keys(obj):
+    """
+    Creates a list composed of the keys of `obj`.
+
+    Args:
+        obj: Object to extract keys from.
+
+    Returns:
+        List of keys.
+
+    Example:
+
+        >>> keys([1, 2, 3])
+        [0, 1, 2]
+        >>> set(keys({"a": 1, "b": 2, "c": 3})) == set(["a", "b", "c"])
+        True
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 1.1.0
+        Added ``keys_in`` as alias.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``keys_in``.
+    """
+    return [key for key, _ in iterator(obj)]
+
+
+@t.overload
+def map_keys(
+    obj: t.Mapping[T, T2], iteratee: t.Callable[[T2, T, t.Dict[T, T2]], T3]
+) -> t.Dict[T3, T2]: ...
+
+
+@t.overload
+def map_keys(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], T3]) -> t.Dict[T3, T2]: ...
+
+
+@t.overload
+def map_keys(obj: t.Mapping[t.Any, T2], iteratee: t.Callable[[T2], T3]) -> t.Dict[T3, T2]: ...
+
+
+@t.overload
+def map_keys(
+    obj: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], T2]
+) -> t.Dict[T2, T]: ...
+
+
+@t.overload
+def map_keys(obj: t.Iterable[T], iteratee: t.Callable[[T, int], T2]) -> t.Dict[T2, T]: ...
+
+
+@t.overload
+def map_keys(obj: t.Iterable[T], iteratee: t.Callable[[T], T2]) -> t.Dict[T2, T]: ...
+
+
+@t.overload
+def map_keys(
+    obj: t.Iterable[t.Any], iteratee: t.Union[IterateeObjT, None] = None
+) -> t.Dict[t.Any, t.Any]: ...
+
+
+def map_keys(obj, iteratee=None):
+    """
+    The opposite of :func:`map_values`, this method creates an object with the same values as object
+    and keys generated by running each own enumerable string keyed property of object through
+    iteratee. The iteratee is invoked with three arguments: ``(value, key, object)``.
+
+    Args:
+        obj: Object to map.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Results of running `obj` through `iteratee`.
+
+    Example:
+
+        >>> callback = lambda value, key: key * 2
+        >>> results = map_keys({"a": 1, "b": 2, "c": 3}, callback)
+        >>> results == {"aa": 1, "bb": 2, "cc": 3}
+        True
+
+    .. versionadded:: 3.3.0
+    """
+    return {result: value for result, value, _, _ in iteriteratee(obj, iteratee)}
+
+
+@t.overload
+def map_values(
+    obj: t.Mapping[T, T2], iteratee: t.Callable[[T2, T, t.Dict[T, T2]], T3]
+) -> t.Dict[T, T3]: ...
+
+
+@t.overload
+def map_values(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], T3]) -> t.Dict[T, T3]: ...
+
+
+@t.overload
+def map_values(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2], T3]) -> t.Dict[T, T3]: ...
+
+
+@t.overload
+def map_values(
+    obj: t.Iterable[T], iteratee: t.Callable[[T, int, t.List[T]], T2]
+) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def map_values(obj: t.Iterable[T], iteratee: t.Callable[[T, int], T2]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def map_values(obj: t.Iterable[T], iteratee: t.Callable[[T], T2]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def map_values(
+    obj: t.Iterable[t.Any], iteratee: t.Union[IterateeObjT, None] = None
+) -> t.Dict[t.Any, t.Any]: ...
+
+
+def map_values(obj, iteratee=None):
+    """
+    Creates an object with the same keys as object and values generated by running each string keyed
+    property of object through iteratee. The iteratee is invoked with three arguments: ``(value,
+    key, object)``.
+
+    Args:
+        obj: Object to map.
+        iteratee: Iteratee applied per iteration.
+
+    Returns:
+        Results of running `obj` through `iteratee`.
+
+    Example:
+
+        >>> results = map_values({"a": 1, "b": 2, "c": 3}, lambda x: x * 2)
+        >>> results == {"a": 2, "b": 4, "c": 6}
+        True
+        >>> results = map_values({"a": 1, "b": {"d": 4}, "c": 3}, {"d": 4})
+        >>> results == {"a": False, "b": True, "c": False}
+        True
+
+    .. versionadded:: 1.0.0
+    """
+    return {key: result for result, _, key, _ in iteriteratee(obj, iteratee)}
+
+
+def map_values_deep(
+    obj: t.Iterable[t.Any],
+    iteratee: t.Union[t.Callable[..., t.Any], None] = None,
+    property_path: t.Any = UNSET,
+) -> t.Any:
+    """
+    Map all non-object values in `obj` with return values from `iteratee`. The iteratee is invoked
+    with two arguments: ``(obj_value, property_path)`` where ``property_path`` contains the list of
+    path keys corresponding to the path of ``obj_value``.
+
+    Args:
+        obj: Object to map.
+        iteratee: Iteratee applied to each value.
+        property_path: Path key(s) to access.
+
+    Returns:
+        The modified object.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> x = {"a": 1, "b": {"c": 2}}
+        >>> y = map_values_deep(x, lambda val: val * 2)
+        >>> y == {"a": 2, "b": {"c": 4}}
+        True
+        >>> z = map_values_deep(x, lambda val, props: props)
+        >>> z == {"a": ["a"], "b": {"c": ["b", "c"]}}
+        True
+
+    .. versionadded: 2.2.0
+
+    .. versionchanged:: 3.0.0
+        Allow iteratees to accept partial arguments.
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``deep_map_values`` to ``map_values_deep``.
+    """
+    properties = to_path(property_path)
+
+    if pyd.is_object(obj):
+
+        def deep_iteratee(value, key):
+            return map_values_deep(value, iteratee, pyd.flatten([properties, key]))
+
+        return assign(obj, map_values(obj, deep_iteratee))  # type: ignore
+    else:
+        return callit(iteratee, obj, properties)
+
+
+def apply(obj: T, func: t.Callable[[T], T2]) -> T2:
+    """
+    Returns the result of calling `func` on `obj`. Particularly useful to pass
+    `obj` through a function during a method chain.
+
+    Args:
+        obj: Object to apply function to
+        func: Function called with `obj`.
+
+    Returns:
+        Results of ``func(value)``.
+
+    Example:
+
+        >>> apply(5, lambda x: x * 2)
+        10
+
+    .. versionadded:: 8.0.0
+    """
+    return func(obj)
+
+
+def apply_if(obj: T, func: t.Callable[[T], T2], predicate: t.Callable[[T], bool]) -> t.Union[T, T2]:
+    """
+    Apply `func` to `obj` if `predicate` returns `True`.
+
+    Args:
+        obj: Object to apply `func` to.
+        func: Function to apply to `obj`.
+        predicate: Predicate applied to `obj`.
+
+    Returns:
+        Result of applying `func` to `obj` or `obj`.
+
+    Example:
+
+        >>> apply_if(2, lambda x: x * 2, lambda x: x > 1)
+        4
+        >>> apply_if(2, lambda x: x * 2, lambda x: x < 1)
+        2
+
+    .. versionadded:: 8.0.0
+    """
+    return func(obj) if predicate(obj) else obj
+
+
+def apply_if_not_none(obj: t.Optional[T], func: t.Callable[[T], T2]) -> t.Optional[T2]:
+    """
+    Apply `func` to `obj` if `obj` is not ``None``.
+
+    Args:
+        obj: Object to apply `func` to.
+        func: Function to apply to `obj`.
+
+    Returns:
+        Result of applying `func` to `obj` or ``None``.
+
+    Example:
+
+        >>> apply_if_not_none(2, lambda x: x * 2)
+        4
+        >>> apply_if_not_none(None, lambda x: x * 2) is None
+        True
+
+    .. versionadded:: 8.0.0
+    """
+    return apply_if(obj, func, lambda x: x is not None)  # type: ignore
+
+
+@t.overload
+def apply_catch(
+    obj: T, func: t.Callable[[T], T2], exceptions: t.Iterable[t.Type[Exception]], default: T3
+) -> t.Union[T2, T3]: ...
+
+
+@t.overload
+def apply_catch(
+    obj: T,
+    func: t.Callable[[T], T2],
+    exceptions: t.Iterable[t.Type[Exception]],
+    default: Unset = UNSET,
+) -> t.Union[T, T2]: ...
+
+
+def apply_catch(obj, func, exceptions, default=UNSET):
+    """
+    Tries to apply `func` to `obj` if any of the exceptions in `excs` are raised, return `default`
+    or `obj` if not set.
+
+    Args:
+        obj: Object to apply `func` to.
+        func: Function to apply to `obj`.
+        excs: Exceptions to catch.
+        default: Value to return if exception is raised.
+
+    Returns:
+        Result of applying `func` to `obj` or ``default``.
+
+    Example:
+
+        >>> apply_catch(2, lambda x: x * 2, [ValueError])
+        4
+        >>> apply_catch(2, lambda x: x / 0, [ZeroDivisionError], "error")
+        'error'
+        >>> apply_catch(2, lambda x: x / 0, [ZeroDivisionError])
+        2
+
+    .. versionadded:: 8.0.0
+    """
+    try:
+        return func(obj)
+    except tuple(exceptions):
+        return obj if default is UNSET else default
+
+
+@t.overload
+def merge(
+    obj: t.Mapping[T, T2], *sources: t.Mapping[T3, T4]
+) -> t.Dict[t.Union[T, T3], t.Union[T2, T4]]: ...
+
+
+@t.overload
+def merge(obj: t.Sequence[T], *sources: t.Sequence[T2]) -> t.List[t.Union[T, T2]]: ...
+
+
+def merge(obj, *sources):
+    """
+    Recursively merges properties of the source object(s) into the destination object. Subsequent
+    sources will overwrite property assignments of previous sources.
+
+    Args:
+        obj: Destination object to merge source(s) into.
+        sources: Source objects to merge from. subsequent sources overwrite previous ones.
+
+    Returns:
+        Merged object.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> obj = {"a": 2}
+        >>> obj2 = merge(obj, {"a": 1}, {"b": 2, "c": 3}, {"d": 4})
+        >>> obj2 == {"a": 1, "b": 2, "c": 3, "d": 4}
+        True
+        >>> obj is obj2
+        True
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 2.3.2
+        Apply :func:`clone_deep` to each `source` before assigning to `obj`.
+
+    .. versionchanged:: 2.3.2
+        Allow `iteratee` to be passed by reference if it is the last positional argument.
+
+    .. versionchanged:: 4.0.0
+        Moved iteratee argument to :func:`merge_with`.
+
+    .. versionchanged:: 4.9.3
+        Fixed regression in v4.8.0 that caused exception when `obj` was ``None``.
+    """
+    return merge_with(obj, *sources)
+
+
+def merge_with(obj: t.Any, *sources: t.Any, **kwargs: t.Any) -> t.Any:
+    """
+    This method is like :func:`merge` except that it accepts customizer which is invoked to produce
+    the merged values of the destination and source properties. If customizer returns ``None``,
+    merging is handled by this method instead. The customizer is invoked with five arguments:
+    ``(obj_value, src_value, key, obj, source)``.
+
+    Args:
+        obj: Destination object to merge source(s) into.
+        sources: Source objects to merge from. subsequent sources
+            overwrite previous ones.
+
+    Keyword Args:
+        iteratee: Iteratee function to handle merging
+            (must be passed in as keyword argument).
+
+    Returns:
+        Merged object.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> cbk = lambda obj_val, src_val: obj_val + src_val
+        >>> obj1 = {"a": [1], "b": [2]}
+        >>> obj2 = {"a": [3], "b": [4]}
+        >>> res = merge_with(obj1, obj2, cbk)
+        >>> obj1 == {"a": [1, 3], "b": [2, 4]}
+        True
+
+    .. versionadded:: 4.0.0
+
+    .. versionchanged:: 4.9.3
+        Fixed regression in v4.8.0 that caused exception when `obj` was ``None``.
+    """
+    if obj is None:
+        return None
+
+    list_sources = list(sources)
+    iteratee = kwargs.pop("iteratee", None)
+
+    if iteratee is None and list_sources and callable(list_sources[-1]):
+        iteratee = list_sources.pop()
+
+    list_sources = [copy.deepcopy(source) for source in list_sources]
+
+    if callable(iteratee):
+        iteratee = partial(callit, iteratee, argcount=getargcount(iteratee, maxargs=5))
+    else:
+        iteratee = None
+
+    return _merge_with(obj, *list_sources, iteratee=iteratee, **kwargs)
+
+
+def _merge_with(obj, *sources, **kwargs):
+    iteratee = kwargs.get("iteratee")
+    setter = kwargs.get("_setter")
+
+    if setter is None:
+        setter = base_set
+
+    for source in sources:
+        for key, src_value in iterator(source):
+            obj_value = base_get(obj, key, default=None)
+            all_sequences = isinstance(src_value, list) and isinstance(obj_value, list)
+            all_mappings = isinstance(src_value, dict) and isinstance(obj_value, dict)
+
+            _result = None
+            if iteratee:
+                _result = iteratee(obj_value, src_value, key, obj, source)
+
+            if _result is not None:
+                result = _result
+            elif all_sequences or all_mappings:
+                result = _merge_with(obj_value, src_value, iteratee=iteratee, _setter=setter)
+            else:
+                result = src_value
+
+            setter(obj, key, result)
+
+    return obj
+
+
+@t.overload
+def omit(obj: t.Mapping[T, T2], *properties: PathT) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def omit(obj: t.Union[t.Iterator[T], t.Sequence[T]], *properties: PathT) -> t.Dict[int, T]: ...
+
+
+@t.overload
+def omit(obj: t.Any, *properties: PathT) -> t.Dict[t.Any, t.Any]: ...
+
+
+def omit(obj, *properties):
+    """
+    The opposite of :func:`pick`. This method creates an object composed of the property paths of
+    `obj` that are not omitted.
+
+    Args:
+        obj: Object to process.
+        *properties: Property values to omit.
+
+    Returns:
+        Results of omitting properties.
+
+    Example:
+
+        >>> omit({"a": 1, "b": 2, "c": 3}, "b", "c") == {"a": 1}
+        True
+        >>> omit({"a": 1, "b": 2, "c": 3}, ["a", "c"]) == {"b": 2}
+        True
+        >>> omit([1, 2, 3, 4], 0, 3) == {1: 2, 2: 3}
+        True
+        >>> omit({"a": {"b": {"c": "d"}}}, "a.b.c") == {"a": {"b": {}}}
+        True
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Moved iteratee argument to :func:`omit_by`.
+
+    .. versionchanged:: 4.2.0
+        Support deep paths.
+    """
+    return omit_by(obj, pyd.flatten(properties))
+
+
+@t.overload
+def omit_by(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], t.Any]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def omit_by(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2], t.Any]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def omit_by(obj: t.Dict[T, T2], iteratee: None = None) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def omit_by(
+    obj: t.Union[t.Iterator[T], t.Sequence[T]], iteratee: t.Callable[[T, int], t.Any]
+) -> t.Dict[int, T]: ...
+
+
+@t.overload
+def omit_by(
+    obj: t.Union[t.Iterator[T], t.Sequence[T]], iteratee: t.Callable[[T], t.Any]
+) -> t.Dict[int, T]: ...
+
+
+@t.overload
+def omit_by(obj: t.List[T], iteratee: None = None) -> t.Dict[int, T]: ...
+
+
+@t.overload
+def omit_by(
+    obj: t.Any, iteratee: t.Union[t.Callable[..., t.Any], None] = None
+) -> t.Dict[t.Any, t.Any]: ...
+
+
+def omit_by(obj, iteratee=None):
+    """
+    The opposite of :func:`pick_by`. This method creates an object composed of the string keyed
+    properties of object that predicate doesn't return truthy for. The predicate is invoked with two
+    arguments: ``(value, key)``.
+
+    Args:
+        obj: Object to process.
+        iteratee: Iteratee used to determine which properties to omit.
+
+    Returns:
+        Results of omitting properties.
+
+    Example:
+
+        >>> omit_by({"a": 1, "b": "2", "c": 3}, lambda v: isinstance(v, int))
+        {'b': '2'}
+
+    .. versionadded:: 4.0.0
+
+    .. versionchanged:: 4.2.0
+        Support deep paths for `iteratee`.
+    """
+    if not callable(iteratee):
+        paths = pyd.map_(iteratee, to_path)
+
+        if any(len(path) > 1 for path in paths):
+            cloned = clone_deep(obj)
+        else:
+            cloned = to_dict(obj)
+
+        def _unset(obj, path):
+            pyd.unset(obj, path)
+            return obj
+
+        ret = pyd.reduce_(paths, _unset, cloned)
+    else:
+        argcount = getargcount(iteratee, maxargs=2)
+
+        ret = {
+            key: value
+            for key, value in iterator(obj)
+            if not callit(iteratee, value, key, argcount=argcount)
+        }
+
+    return ret
+
+
+def parse_int(value: t.Any, radix: t.Union[int, None] = None) -> t.Union[int, None]:
+    """
+    Converts the given `value` into an integer of the specified `radix`. If `radix` is falsey, a
+    radix of ``10`` is used unless the `value` is a hexadecimal, in which case a radix of 16 is
+    used.
+
+    Args:
+        value: Value to parse.
+        radix: Base to convert to.
+
+    Returns:
+        Integer if parsable else ``None``.
+
+    Example:
+
+        >>> parse_int("5")
+        5
+        >>> parse_int("12", 8)
+        10
+        >>> parse_int("x") is None
+        True
+
+    .. versionadded:: 1.0.0
+    """
+    if not radix and pyd.is_string(value):
+        try:
+            # Check if value is hexadecimal and if so use base-16 conversion.
+            int(value, 16)
+        except ValueError:
+            pass
+        else:
+            radix = 16
+
+    if not radix:
+        radix = 10
+
+    try:
+        # NOTE: Must convert value to string when supplying radix to int(). Dropping radix arg when
+        # 10 is needed to allow floats to parse correctly.
+        args = (value,) if radix == 10 else (to_string(value), radix)
+        parsed = int(*args)
+    except (ValueError, TypeError):
+        parsed = None
+
+    return parsed
+
+
+@t.overload
+def pick(obj: t.Mapping[T, T2], *properties: PathT) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def pick(obj: t.Union[t.Tuple[T, ...], t.List[T]], *properties: PathT) -> t.Dict[int, T]: ...
+
+
+@t.overload
+def pick(obj: t.Any, *properties: PathT) -> t.Dict[t.Any, t.Any]: ...
+
+
+def pick(obj, *properties):
+    """
+    Creates an object composed of the picked object properties.
+
+    Args:
+        obj: Object to pick from.
+        properties: Property values to pick.
+
+    Returns:
+        Dict containing picked properties.
+
+    Example:
+
+        >>> pick({"a": 1, "b": 2, "c": 3}, "a", "b") == {"a": 1, "b": 2}
+        True
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Moved iteratee argument to :func:`pick_by`.
+    """
+    return pick_by(obj, pyd.flatten(properties))
+
+
+@t.overload
+def pick_by(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2], t.Any]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def pick_by(obj: t.Mapping[T, T2], iteratee: t.Callable[[T2, T], t.Any]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def pick_by(obj: t.Dict[T, T2], iteratee: None = None) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def pick_by(
+    obj: t.Union[t.Tuple[T, ...], t.List[T]], iteratee: t.Callable[[T, int], t.Any]
+) -> t.Dict[int, T]: ...
+
+
+@t.overload
+def pick_by(
+    obj: t.Union[t.Tuple[T, ...], t.List[T]], iteratee: t.Callable[[T], t.Any]
+) -> t.Dict[int, T]: ...
+
+
+@t.overload
+def pick_by(obj: t.Union[t.Tuple[T, ...], t.List[T]], iteratee: None = None) -> t.Dict[int, T]: ...
+
+
+@t.overload
+def pick_by(
+    obj: t.Any, iteratee: t.Union[t.Callable[..., t.Any], None] = None
+) -> t.Dict[t.Any, t.Any]: ...
+
+
+def pick_by(obj, iteratee=None):
+    """
+    Creates an object composed of the object properties predicate returns truthy for. The predicate
+    is invoked with two arguments: ``(value, key)``.
+
+    Args:
+        obj: Object to pick from.
+        iteratee: Iteratee used to determine which properties to pick.
+
+    Returns:
+        Dict containing picked properties.
+
+    Example:
+
+        >>> obj = {"a": 1, "b": "2", "c": 3}
+        >>> pick_by(obj, lambda v: isinstance(v, int)) == {"a": 1, "c": 3}
+        True
+
+    .. versionadded:: 4.0.0
+    """
+    obj = to_dict(obj)
+
+    if iteratee is None or callable(iteratee):
+        paths = keys(obj)
+        if iteratee is None:
+            iteratee = pyd.identity
+            argcount = 1
+        else:
+            argcount = getargcount(iteratee, maxargs=2)
+    else:
+        paths = iteratee if iteratee is not None else []
+
+        def iteratee(value, path):  # pylint: disable=function-redefined
+            return has(obj, path)
+
+        argcount = 2
+
+    result = {}
+
+    for path in paths:
+        value = get(obj, path)
+
+        if callit(iteratee, value, path, argcount=argcount):
+            set_(result, path, value)
+
+    return result
+
+
+def rename_keys(obj: t.Dict[T, T2], key_map: t.Dict[t.Any, T3]) -> t.Dict[t.Union[T, T3], T2]:
+    """
+    Rename the keys of `obj` using `key_map` and return new object.
+
+    Args:
+        obj: Object to rename.
+        key_map: Renaming map whose keys correspond to existing keys in `obj` and whose
+            values are the new key name.
+
+    Returns:
+        Renamed `obj`.
+
+    Example:
+
+        >>> obj = rename_keys({"a": 1, "b": 2, "c": 3}, {"a": "A", "b": "B"})
+        >>> obj == {"A": 1, "B": 2, "c": 3}
+        True
+
+    .. versionadded:: 2.0.0
+    """
+    return {key_map.get(key, key): value for key, value in obj.items()}
+
+
+def set_(obj: T, path: PathT, value: t.Any) -> T:
+    """
+    Sets the value of an object described by `path`. If any part of the object path doesn't exist,
+    it will be created.
+
+    Args:
+        obj: Object to modify.
+        path: Target path to set value to.
+        value: Value to set.
+
+    Returns:
+        Modified `obj`.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> set_({}, "a.b.c", 1)
+        {'a': {'b': {'c': 1}}}
+        >>> set_({}, "a.0.c", 1)
+        {'a': {'0': {'c': 1}}}
+        >>> set_([1, 2], "[2][0]", 1)
+        [1, 2, [1]]
+        >>> set_({}, "a.b[0].c", 1)
+        {'a': {'b': [{'c': 1}]}}
+
+    .. versionadded:: 2.2.0
+
+    .. versionchanged:: 3.3.0
+        Added :func:`set_` as main definition and :func:`deep_set` as alias.
+
+    .. versionchanged:: 4.0.0
+
+        - Modify `obj` in place.
+        - Support creating default path values as ``list`` or ``dict`` based on whether key or index
+          substrings are used.
+        - Remove alias ``deep_set``.
+    """
+    return set_with(obj, path, value)
+
+
+def set_with(
+    obj: T, path: PathT, value: t.Any, customizer: t.Union[t.Callable[..., t.Any], None] = None
+) -> T:
+    """
+    This method is like :func:`set_` except that it accepts customizer which is invoked to produce
+    the objects of path. If customizer returns undefined path creation is handled by the method
+    instead. The customizer is invoked with three arguments: ``(nested_value, key, nested_object)``.
+
+    Args:
+        obj: Object to modify.
+        path: Target path to set value to.
+        value: Value to set.
+        customizer: The function to customize assigned values.
+
+    Returns:
+        Modified `obj`.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> set_with({}, "[0][1]", "a", lambda: {})
+        {0: {1: 'a'}}
+
+    .. versionadded:: 4.0.0
+
+    .. versionchanged:: 4.3.1
+        Fixed bug where a callable `value` was called when being set.
+    """
+    return update_with(obj, path, pyd.constant(value), customizer=customizer)
+
+
+def to_boolean(
+    obj: t.Any,
+    true_values: t.Tuple[str, ...] = ("true", "1"),
+    false_values: t.Tuple[str, ...] = ("false", "0"),
+) -> t.Union[bool, None]:
+    """
+    Convert `obj` to boolean. This is not like the builtin ``bool`` function. By default, commonly
+    considered strings values are converted to their boolean equivalent, i.e., ``'0'`` and
+    ``'false'`` are converted to ``False`` while ``'1'`` and ``'true'`` are converted to ``True``.
+    If a string value is provided that isn't recognized as having a common boolean conversion, then
+    the returned value is ``None``. Non-string values of `obj` are converted using ``bool``.
+    Optionally, `true_values` and `false_values` can be overridden but each value must be a string.
+
+    Args:
+        obj: Object to convert.
+        true_values: Values to consider ``True``. Each value must be a string.
+            Comparision is case-insensitive. Defaults to ``('true', '1')``.
+        false_values: Values to consider ``False``. Each value must be a string.
+            Comparision is case-insensitive. Defaults to ``('false', '0')``.
+
+    Returns:
+        Boolean value of `obj`.
+
+    Example:
+
+        >>> to_boolean("true")
+        True
+        >>> to_boolean("1")
+        True
+        >>> to_boolean("false")
+        False
+        >>> to_boolean("0")
+        False
+        >>> assert to_boolean("a") is None
+
+    .. versionadded:: 3.0.0
+    """
+    if pyd.is_string(obj):
+        obj = obj.strip()
+
+        def boolean_match(text, vals):
+            if text.lower() in [val.lower() for val in vals]:
+                return True
+            else:
+                return re.match("|".join(vals), text)
+
+        if true_values and boolean_match(obj, true_values):
+            value = True
+        elif false_values and boolean_match(obj, false_values):
+            value = False
+        else:
+            value = None
+    else:
+        value = bool(obj)
+
+    return value
+
+
+@t.overload
+def to_dict(obj: t.Mapping[T, T2]) -> t.Dict[T, T2]: ...
+
+
+@t.overload
+def to_dict(obj: t.Union[t.Iterator[T], t.Sequence[T]]) -> t.Dict[int, T]: ...
+
+
+@t.overload
+def to_dict(obj: t.Any) -> t.Dict[t.Any, t.Any]: ...
+
+
+def to_dict(obj):
+    """
+    Convert `obj` to ``dict`` by creating a new ``dict`` using `obj` keys and values.
+
+    Args:
+        obj: Object to convert.
+
+    Returns:
+        Object converted to ``dict``.
+
+    Example:
+
+        >>> obj = {"a": 1, "b": 2}
+        >>> obj2 = to_dict(obj)
+        >>> obj2 == obj
+        True
+        >>> obj2 is not obj
+        True
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``to_plain_object``.
+
+    .. versionchanged:: 4.2.0
+        Use ``pydash.helpers.iterator`` to generate key/value pairs.
+
+    .. versionchanged:: 4.7.1
+        Try to convert to ``dict`` using ``dict()`` first, then fallback to using
+        ``pydash.helpers.iterator``.
+    """
+    return dict(iterator(obj))
+
+
+def to_integer(obj: t.Any) -> int:
+    """
+    Converts `obj` to an integer.
+
+    Args:
+        obj: Object to convert.
+
+    Returns:
+        Converted integer or ``0`` if it can't be converted.
+
+    Example:
+
+        >>> to_integer(3.2)
+        3
+        >>> to_integer("3.2")
+        3
+        >>> to_integer("3.9")
+        3
+        >>> to_integer("invalid")
+        0
+
+    .. versionadded:: 4.0.0
+    """
+    try:
+        # Convert to float first to handle converting floats as string since int('1.1') would fail
+        # but this won't.
+        num = int(float(obj))
+    except (ValueError, TypeError):
+        num = 0
+
+    return num
+
+
+@t.overload
+def to_list(obj: t.Dict[t.Any, T], split_strings: bool = True) -> t.List[T]: ...
+
+
+@t.overload
+def to_list(obj: t.Iterable[T], split_strings: bool = True) -> t.List[T]: ...
+
+
+@t.overload
+def to_list(obj: T, split_strings: bool = True) -> t.List[T]: ...
+
+
+def to_list(obj, split_strings=True):
+    """
+    Converts an obj, an iterable or a single item to a list.
+
+    Args:
+        obj: Object to convert item or wrap.
+        split_strings: Whether to split strings into single chars. Defaults to
+            ``True``.
+
+    Returns:
+        Converted obj or wrapped item.
+
+    Example:
+
+        >>> results = to_list({"a": 1, "b": 2, "c": 3})
+        >>> assert set(results) == set([1, 2, 3])
+
+        >>> to_list((1, 2, 3, 4))
+        [1, 2, 3, 4]
+
+        >>> to_list(1)
+        [1]
+
+        >>> to_list([1])
+        [1]
+
+        >>> to_list(a for a in [1, 2, 3])
+        [1, 2, 3]
+
+        >>> to_list("cat")
+        ['c', 'a', 't']
+
+        >>> to_list("cat", split_strings=False)
+        ['cat']
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.3.0
+
+        - Wrap non-iterable items in a list.
+        - Convert other iterables to list.
+        - Byte objects are returned as single character strings in Python 3.
+    """
+    if isinstance(obj, list):
+        return obj[:]
+    elif isinstance(obj, dict):
+        return obj.values()
+    elif not split_strings and isinstance(obj, (str, bytes)):
+        return [obj]
+    elif split_strings and isinstance(obj, bytes):
+        # in python3 iterating over bytes gives integers instead of strings
+        return list(chr(c) if isinstance(c, int) else c for c in obj)
+    else:
+        try:
+            return list(obj)
+        except TypeError:
+            return [obj]
+
+
+def to_number(obj: t.Any, precision: int = 0) -> t.Union[float, None]:
+    """
+    Convert `obj` to a number. All numbers are retuned as ``float``. If precision is negative, round
+    `obj` to the nearest positive integer place. If `obj` can't be converted to a number, ``None``
+    is returned.
+
+    Args:
+        obj: Object to convert.
+        precision: Precision to round number to. Defaults to ``0``.
+
+    Returns:
+        Converted number or ``None`` if it can't be converted.
+
+    Example:
+
+        >>> to_number("1234.5678")
+        1235.0
+        >>> to_number("1234.5678", 4)
+        1234.5678
+        >>> to_number(1, 2)
+        1.0
+
+    .. versionadded:: 3.0.0
+    """
+    try:
+        factor = pow(10, precision)
+
+        if precision < 0:
+            # Round down since negative `precision` means we are going to the nearest positive
+            # integer place.
+            rounder: t.Callable[..., t.Any] = math.floor
+        else:
+            rounder = round
+
+        num = rounder(float(obj) * factor) / factor
+    except Exception:
+        num = None
+
+    return num
+
+
+@t.overload
+def to_pairs(obj: t.Mapping[T, T2]) -> t.List[t.Tuple[T, T2]]: ...
+
+
+@t.overload
+def to_pairs(obj: t.Union[t.Iterator[T], t.Sequence[T]]) -> t.List[t.Tuple[int, T]]: ...
+
+
+@t.overload
+def to_pairs(obj: t.Any) -> t.List[t.Any]: ...
+
+
+def to_pairs(obj):
+    """
+    Creates a list of tuples of an object's key-value pairs, i.e.,
+    ``[(key1, value1), (key2, value2)]``.
+
+    Args:
+        obj: Object to process.
+
+    Returns:
+        List of tuples of the object's key-value pairs.
+
+    Example:
+
+        >>> to_pairs([1, 2, 3, 4])
+        [(0, 1), (1, 2), (2, 3), (3, 4)]
+        >>> to_pairs({"a": 1})
+        [('a', 1)]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``pairs`` to ``to_pairs``.
+
+    .. versionchanged:: 8.0.0
+        Returning list of tuples instead of list of lists.
+    """
+    return [(key, value) for key, value in iterator(obj)]
+
+
+def to_string(obj: t.Any) -> str:
+    """
+    Converts an object to string.
+
+    Args:
+        obj: Object to convert.
+
+    Returns:
+        String representation of `obj`.
+
+    Example:
+
+        >>> to_string(1) == "1"
+        True
+        >>> to_string(None) == ""
+        True
+        >>> to_string([1, 2, 3]) == "[1, 2, 3]"
+        True
+        >>> to_string("a") == "a"
+        True
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 3.0.0
+        Convert ``None`` to empty string.
+    """
+    if pyd.is_string(obj):
+        res = obj
+    elif obj is None:
+        res = ""
+    else:
+        res = str(obj)
+    return res
+
+
+@t.overload
+def transform(
+    obj: t.Mapping[T, T2], iteratee: t.Callable[[T3, T2, T, t.Dict[T, T2]], t.Any], accumulator: T3
+) -> T3: ...
+
+
+@t.overload
+def transform(
+    obj: t.Mapping[T, T2], iteratee: t.Callable[[T3, T2, T], t.Any], accumulator: T3
+) -> T3: ...
+
+
+@t.overload
+def transform(
+    obj: t.Mapping[t.Any, T2], iteratee: t.Callable[[T3, T2], t.Any], accumulator: T3
+) -> T3: ...
+
+
+@t.overload
+def transform(
+    obj: t.Mapping[t.Any, t.Any], iteratee: t.Callable[[T3], t.Any], accumulator: T3
+) -> T3: ...
+
+
+@t.overload
+def transform(
+    obj: t.Iterable[T], iteratee: t.Callable[[T3, T, int, t.List[T]], t.Any], accumulator: T3
+) -> T3: ...
+
+
+@t.overload
+def transform(
+    obj: t.Iterable[T], iteratee: t.Callable[[T3, T, int], t.Any], accumulator: T3
+) -> T3: ...
+
+
+@t.overload
+def transform(obj: t.Iterable[T], iteratee: t.Callable[[T3, T], t.Any], accumulator: T3) -> T3: ...
+
+
+@t.overload
+def transform(obj: t.Iterable[t.Any], iteratee: t.Callable[[T3], t.Any], accumulator: T3) -> T3: ...
+
+
+@t.overload
+def transform(obj: t.Any, iteratee: t.Any = None, accumulator: t.Any = None) -> t.Any: ...
+
+
+def transform(obj, iteratee=None, accumulator=None):
+    """
+    An alernative to :func:`pydash.collections.reduce`, this method transforms `obj` to a new
+    accumulator object which is the result of running each of its properties through an iteratee,
+    with each iteratee execution potentially mutating the accumulator object. The iteratee is
+    invoked with four arguments: ``(accumulator, value, key, object)``. Iteratees may exit iteration
+    early by explicitly returning ``False``.
+
+    Args:
+        obj: Object to process.
+        iteratee: Iteratee applied per iteration.
+        accumulator: Accumulated object. Defaults to ``list``.
+
+    Returns:
+        Accumulated object.
+
+    Example:
+
+        >>> transform([1, 2, 3, 4], lambda acc, v, k: acc.append((k, v)))
+        [(0, 1), (1, 2), (2, 3), (3, 4)]
+
+    .. versionadded:: 1.0.0
+    """
+    if iteratee is None:
+        iteratee = pyd.identity
+        argcount = 1
+    else:
+        argcount = getargcount(iteratee, maxargs=4)
+
+    if accumulator is None:
+        accumulator = []
+
+    walk = (
+        None
+        for key, item in iterator(obj)
+        if callit(iteratee, accumulator, item, key, obj, argcount=argcount) is False
+    )
+    next(walk, None)
+
+    return accumulator
+
+
+@t.overload
+def update(
+    obj: t.Dict[t.Any, T2],
+    path: PathT,
+    updater: t.Callable[[T2], t.Any],
+) -> t.Dict[t.Any, t.Any]: ...
+
+
+@t.overload
+def update(
+    obj: t.List[T],
+    path: PathT,
+    updater: t.Callable[[T], t.Any],
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def update(
+    obj: T,
+    path: PathT,
+    updater: t.Callable[..., t.Any],
+) -> T: ...
+
+
+def update(obj, path, updater):
+    """
+    This method is like :func:`set_` except that accepts updater to produce the value to set. Use
+    :func:`update_with` to customize path creation. The updater is invoked with one argument:
+    ``(value)``.
+
+    Args:
+        obj: Object to modify.
+        path: A string or list of keys that describe the object path to modify.
+        updater: Function that returns updated value.
+
+    Returns:
+        Updated `obj`.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> update({}, ["a", "b"], lambda value: value)
+        {'a': {'b': None}}
+        >>> update([], [0, 0], lambda value: 1)
+        [[1]]
+
+    .. versionadded:: 4.0.0
+    """
+    return update_with(obj, path, updater)
+
+
+@t.overload
+def update_with(
+    obj: t.Dict[t.Any, T2],
+    path: PathT,
+    updater: t.Callable[[T2], t.Any],
+    customizer: t.Union[t.Callable[..., t.Any], None],
+) -> t.Dict[t.Any, t.Any]: ...
+
+
+@t.overload
+def update_with(
+    obj: t.List[T],
+    path: PathT,
+    updater: t.Callable[[T], t.Any],
+    customizer: t.Union[t.Callable[..., t.Any], None] = None,
+) -> t.List[t.Any]: ...
+
+
+@t.overload
+def update_with(
+    obj: T,
+    path: PathT,
+    updater: t.Callable[..., t.Any],
+    customizer: t.Union[t.Callable[..., t.Any], None] = None,
+) -> T: ...
+
+
+def update_with(obj, path, updater, customizer=None):  # noqa: PLR0912
+    """
+    This method is like :func:`update` except that it accepts customizer which is invoked to produce
+    the objects of path. If customizer returns ``None``, path creation is handled by the method
+    instead. The customizer is invoked with three arguments: ``(nested_value, key, nested_object)``.
+
+    Args:
+        obj: Object to modify.
+        path: A string or list of keys that describe the object path to modify.
+        updater: Function that returns updated value.
+        customizer: The function to customize assigned values.
+
+    Returns:
+        Updated `obj`.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> update_with({}, "[0][1]", lambda: "a", lambda: {})
+        {0: {1: 'a'}}
+
+    .. versionadded:: 4.0.0
+    """
+    if not callable(updater):
+        updater = pyd.constant(updater)
+
+    if customizer is not None and not callable(customizer):
+        call_customizer = partial(callit, clone, customizer, argcount=1)
+    elif customizer:
+        call_customizer = partial(callit, customizer, argcount=getargcount(customizer, maxargs=3))
+    else:
+        call_customizer = None
+
+    default_type = dict if isinstance(obj, dict) else list
+    tokens = to_path_tokens(path)
+
+    last_key = pyd.last(tokens)
+
+    if isinstance(last_key, PathToken):
+        last_key = last_key.key
+
+    target = obj
+
+    for idx, token in enumerate(pyd.initial(tokens)):
+        key = token.key
+        default_factory = pyd.get(tokens, [idx + 1, "default_factory"], default=default_type)
+
+        obj_val = base_get(target, key, default=None)
+        path_obj = None
+
+        if call_customizer:
+            path_obj = call_customizer(obj_val, key, target)
+
+        if path_obj is None:
+            path_obj = default_factory()
+
+        base_set(target, key, path_obj, allow_override=False)
+
+        try:
+            target = base_get(target, key, default=None)
+        except TypeError as exc:  # pragma: no cover
+            try:
+                target = target[int(key)]
+                _failed = False
+            except Exception:
+                _failed = True
+
+            if _failed:
+                raise TypeError(f"Unable to update object at index {key!r}. {exc}") from exc
+
+    value = base_get(target, last_key, default=None)
+    base_set(target, last_key, callit(updater, value))
+
+    return obj
+
+
+def unset(obj: t.Union[t.List[t.Any], t.Dict[t.Any, t.Any]], path: PathT) -> bool:  # noqa: C901
+    """
+    Removes the property at `path` of `obj`.
+
+    Note:
+        Only ``list``, ``dict``, or objects with a ``pop()`` method can be unset by this function.
+
+    Args:
+        obj: The object to modify.
+        path: The path of the property to unset.
+
+    Returns:
+        Whether the property was deleted.
+
+    Warning:
+        `obj` is modified in place.
+
+    Example:
+
+        >>> obj = {"a": [{"b": {"c": 7}}]}
+        >>> unset(obj, "a[0].b.c")
+        True
+        >>> obj
+        {'a': [{'b': {}}]}
+        >>> unset(obj, "a[0].b.c")
+        False
+    """
+    tokens = to_path_tokens(path)
+
+    last_key = pyd.last(tokens)
+
+    if isinstance(last_key, PathToken):
+        last_key = last_key.key
+
+    target = obj
+
+    for token in pyd.initial(tokens):
+        key = token.key
+
+        try:
+            try:
+                target = target[key]
+            except TypeError:
+                target = target[int(key)]
+        except Exception:
+            # Allow different types reassignment
+            target = UNSET  # type: ignore
+
+        if target is UNSET:
+            break
+
+    did_unset = False
+
+    if target is not UNSET:
+        try:
+            try:
+                # last_key can be a lot of things
+                # safe as everything wrapped in try/except
+                target.pop(last_key)  # type: ignore
+                did_unset = True
+            except TypeError:
+                target.pop(int(last_key))  # type: ignore
+                did_unset = True
+        except Exception:
+            pass
+
+    return did_unset
+
+
+@t.overload
+def values(obj: t.Mapping[t.Any, T2]) -> t.List[T2]: ...
+
+
+@t.overload
+def values(obj: t.Iterable[T]) -> t.List[T]: ...
+
+
+@t.overload
+def values(obj: t.Any) -> t.List[t.Any]: ...
+
+
+def values(obj):
+    """
+    Creates a list composed of the values of `obj`.
+
+    Args:
+        obj: Object to extract values from.
+
+    Returns:
+        List of values.
+
+    Example:
+
+        >>> results = values({"a": 1, "b": 2, "c": 3})
+        >>> set(results) == set([1, 2, 3])
+        True
+        >>> values([2, 4, 6, 8])
+        [2, 4, 6, 8]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 1.1.0
+        Added ``values_in`` as alias.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``values_in``.
+    """
+    return [value for _, value in iterator(obj)]
+
+
+#
+# Utility methods not a part of the main API
+#
+
+
+def base_clone(value, is_deep=False, customizer=None, key=None, _cloned=False):
+    """Base clone function that supports deep clone and customizer callback."""
+    clone_by = copy.deepcopy if is_deep else copy.copy
+    result = None
+
+    if callable(customizer) and not _cloned:
+        argcount = getargcount(customizer, maxargs=4)
+        cbk = partial(callit, customizer, argcount=argcount)
+    elif _cloned:
+        cbk = customizer
+    else:
+        cbk = None
+
+    if cbk:
+        result = cbk(value, key, value)
+
+    if result is not None:
+        return result
+
+    if not _cloned:
+        result = clone_by(value)
+    else:
+        result = value
+
+    if cbk and not pyd.is_string(value) and not isinstance(value, bytes):
+        for key, subvalue in iterator(value):  # noqa: PLR1704
+            if is_deep:
+                val = base_clone(subvalue, is_deep, cbk, key, _cloned=True)
+            else:
+                val = cbk(subvalue, key, value)
+
+            if val is not None:
+                result[key] = val
+
+    return result
diff --git a/.venv/lib/python3.12/site-packages/pydash/predicates.py b/.venv/lib/python3.12/site-packages/pydash/predicates.py
new file mode 100644
index 00000000..1c6b457d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/predicates.py
@@ -0,0 +1,1664 @@
+"""
+Predicate functions that return boolean evaluations of objects.
+
+.. versionadded:: 2.0.0
+"""
+
+from __future__ import annotations
+
+from collections.abc import Iterable, Mapping
+import datetime
+from itertools import islice
+import json
+import operator
+import re
+from types import BuiltinFunctionType
+import typing as t
+
+from typing_extensions import TypeGuard
+
+import pydash as pyd
+
+from .helpers import BUILTINS, NUMBER_TYPES, UNSET, base_get, callit, iterator
+
+
+if t.TYPE_CHECKING:
+    from _typeshed import (  # pragma: no cover
+        SupportsDunderGE,
+        SupportsDunderGT,
+        SupportsDunderLE,
+        SupportsDunderLT,
+        SupportsRichComparison,
+    )
+
+
+__all__ = (
+    "eq",
+    "eq_cmp",
+    "gt",
+    "gt_cmp",
+    "gte",
+    "gte_cmp",
+    "lt",
+    "lt_cmp",
+    "lte",
+    "lte_cmp",
+    "in_range",
+    "in_range_cmp",
+    "is_associative",
+    "is_blank",
+    "is_boolean",
+    "is_builtin",
+    "is_date",
+    "is_decreasing",
+    "is_dict",
+    "is_empty",
+    "is_equal",
+    "is_equal_cmp",
+    "is_equal_with",
+    "is_equal_with_cmp",
+    "is_error",
+    "is_even",
+    "is_float",
+    "is_function",
+    "is_increasing",
+    "is_indexed",
+    "is_instance_of",
+    "is_instance_of_cmp",
+    "is_integer",
+    "is_iterable",
+    "is_json",
+    "is_list",
+    "is_match",
+    "is_match_cmp",
+    "is_match_with",
+    "is_match_with_cmp",
+    "is_monotone",
+    "is_monotone_cmp",
+    "is_nan",
+    "is_negative",
+    "is_none",
+    "is_number",
+    "is_object",
+    "is_odd",
+    "is_positive",
+    "is_reg_exp",
+    "is_set",
+    "is_strictly_decreasing",
+    "is_strictly_increasing",
+    "is_string",
+    "is_tuple",
+    "is_zero",
+)
+
+T = t.TypeVar("T")
+T2 = t.TypeVar("T2")
+T3 = t.TypeVar("T3")
+
+RegExp = type(re.compile(""))
+
+
+def eq(value: t.Any, other: t.Any) -> bool:
+    """
+    Checks if :attr:`value` is equal to :attr:`other`.
+
+    Args:
+        value: Value to compare.
+        other: Other value to compare.
+
+    Returns:
+        Whether :attr:`value` is equal to :attr:`other`.
+
+    Example:
+
+        >>> eq(None, None)
+        True
+        >>> eq(None, "")
+        False
+        >>> eq("a", "a")
+        True
+        >>> eq(1, str(1))
+        False
+
+    .. versionadded:: 4.0.0
+    """
+    return value is other
+
+
+def eq_cmp(other: T) -> t.Callable[[T], bool]:
+    """
+    Curried version of :func:`eq`.
+
+    Args:
+        other: Value to compare.
+
+    Returns:
+        A predicate checking whether passed :attr:`value` is equal to :attr:`other`.
+
+    Example:
+
+        >>> eq_cmp(None)(None)
+        True
+        >>> eq_cmp(None)("")
+        False
+        >>> eq_cmp("a")("a")
+        True
+        >>> eq_cmp(1)(str(1))
+        False
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda value: eq(value, other)
+
+
+def gt(value: "SupportsDunderGT[T]", other: T) -> bool:
+    """
+    Checks if `value` is greater than `other`.
+
+    Args:
+        value: Value to compare.
+        other: Other value to compare.
+
+    Returns:
+        Whether `value` is greater than `other`.
+
+    Example:
+
+        >>> gt(5, 3)
+        True
+        >>> gt(3, 5)
+        False
+        >>> gt(5, 5)
+        False
+
+    .. versionadded:: 3.3.0
+    """
+    return value > other
+
+
+def gt_cmp(other: T) -> t.Callable[["SupportsDunderGT[T]"], bool]:
+    """
+    Curried version of :func:`gt`.
+
+    Args:
+        other: Value to compare.
+
+    Returns:
+        A predicate checking whether passed :attr:`value` is greater than :attr:`other`.
+
+    Example:
+
+        >>> gt_cmp(3)(5)
+        True
+        >>> gt_cmp(5)(3)
+        False
+        >>> gt_cmp(5)(5)
+        False
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda value: gt(value, other)
+
+
+def gte(value: "SupportsDunderGE[T]", other: T) -> bool:
+    """
+    Checks if `value` is greater than or equal to `other`.
+
+    Args:
+        value: Value to compare.
+        other: Other value to compare.
+
+    Returns:
+        Whether `value` is greater than or equal to `other`.
+
+    Example:
+
+        >>> gte(5, 3)
+        True
+        >>> gte(3, 5)
+        False
+        >>> gte(5, 5)
+        True
+
+    .. versionadded:: 3.3.0
+    """
+    return value >= other
+
+
+def gte_cmp(other: T) -> t.Callable[["SupportsDunderGE[T]"], bool]:
+    """
+    Curried version of :func:`gte`.
+
+    Args:
+        other: Value to compare.
+
+    Returns:
+        A predicate checking whether passed :attr:`value` is greater than or equal to :attr:`other`.
+
+    Example:
+
+        >>> gte_cmp(3)(5)
+        True
+        >>> gte_cmp(5)(3)
+        False
+        >>> gte_cmp(5)(5)
+        True
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda value: gte(value, other)
+
+
+def lt(value: "SupportsDunderLT[T]", other: T) -> bool:
+    """
+    Checks if `value` is less than `other`.
+
+    Args:
+        value: Value to compare.
+        other: Other value to compare.
+
+    Returns:
+        Whether `value` is less than `other`.
+
+    Example:
+
+        >>> lt(5, 3)
+        False
+        >>> lt(3, 5)
+        True
+        >>> lt(5, 5)
+        False
+
+    .. versionadded:: 3.3.0
+    """
+    return value < other
+
+
+def lt_cmp(other: T) -> t.Callable[["SupportsDunderLT[T]"], bool]:
+    """
+    Curried version of :func:`lt`.
+
+    Args:
+        other: Value to compare.
+
+    Returns:
+        A predicate checking whether passed :attr:`value` is less than :attr:`other`.
+
+    Example:
+
+        >>> lt_cmp(3)(5)
+        False
+        >>> lt_cmp(5)(3)
+        True
+        >>> lt_cmp(5)(5)
+        False
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda value: lt(value, other)
+
+
+def lte(value: "SupportsDunderLE[T]", other: T) -> bool:
+    """
+    Checks if `value` is less than or equal to `other`.
+
+    Args:
+        value: Value to compare.
+        other: Other value to compare.
+
+    Returns:
+        Whether `value` is less than or equal to `other`.
+
+    Example:
+
+        >>> lte(5, 3)
+        False
+        >>> lte(3, 5)
+        True
+        >>> lte(5, 5)
+        True
+
+    .. versionadded:: 3.3.0
+    """
+    return value <= other
+
+
+def lte_cmp(other: T) -> t.Callable[["SupportsDunderLE[T]"], bool]:
+    """
+    Curried version of :func:`lte`.
+
+    Args:
+        other: Value to compare.
+
+    Returns:
+        A predicate checking whether passed :attr:`value` is less than or equal to :attr:`other`.
+
+    Example:
+
+        >>> lte_cmp(3)(5)
+        False
+        >>> lte_cmp(5)(3)
+        True
+        >>> lte_cmp(5)(5)
+        True
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda value: lte(value, other)
+
+
+def in_range(value: t.Any, start: t.Any = 0, end: t.Any = None) -> bool:
+    """
+    Checks if `value` is between `start` and up to but not including `end`. If `end` is not
+    specified it defaults to `start` with `start` becoming ``0``.
+
+    Args:
+
+        value: Number to check.
+        start: Start of range inclusive. Defaults to ``0``.
+        end: End of range exclusive. Defaults to `start`.
+
+    Returns:
+        Whether `value` is in range.
+
+    Example:
+
+        >>> in_range(2, 4)
+        True
+        >>> in_range(4, 2)
+        False
+        >>> in_range(2, 1, 3)
+        True
+        >>> in_range(3, 1, 2)
+        False
+        >>> in_range(2.5, 3.5)
+        True
+        >>> in_range(3.5, 2.5)
+        False
+
+    .. versionadded:: 3.1.0
+    """
+    if not is_number(value):
+        return False
+
+    if not is_number(start):
+        start = 0
+
+    if end is None:
+        end = start
+        start = 0
+    elif not is_number(end):
+        end = 0
+
+    return start <= value < end
+
+
+def in_range_cmp(start: t.Any = 0, end: t.Any = None) -> t.Callable[[t.Any], bool]:
+    """
+    Curried version of :func:`in_range`.
+
+    Args:
+        start: Start of range inclusive. Defaults to ``0``.
+        end: End of range exclusive. Defaults to `start`.
+
+    Returns:
+        A predicate checking whether passed :attr:`value` is in range.
+
+    Example:
+
+        >>> in_range_cmp(4)(2)
+        True
+        >>> in_range_cmp(2)(4)
+        False
+        >>> in_range_cmp(1, 3)(2)
+        True
+        >>> in_range_cmp(1, 2)(3)
+        False
+        >>> in_range_cmp(3.5)(2.5)
+        True
+        >>> in_range_cmp(2.5)(3.5)
+        False
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda value: in_range(value, start, end)
+
+
+def is_associative(value: t.Any) -> bool:
+    """
+    Checks if `value` is an associative object meaning that it can be accessed via an index or key.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is associative.
+
+    Example:
+
+        >>> is_associative([])
+        True
+        >>> is_associative({})
+        True
+        >>> is_associative(1)
+        False
+        >>> is_associative(True)
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return hasattr(value, "__getitem__")
+
+
+def is_blank(text: t.Any) -> TypeGuard[str]:
+    r"""
+    Checks if `text` contains only whitespace characters.
+
+    Args:
+        text: String to test.
+
+    Returns:
+        Whether `text` is blank.
+
+    Example:
+
+        >>> is_blank("")
+        True
+        >>> is_blank(" \r\n ")
+        True
+        >>> is_blank(False)
+        False
+
+    .. versionadded:: 3.0.0
+    """
+    try:
+        ret = bool(re.match(r"^(\s+)?$", text))
+    except TypeError:
+        ret = False
+
+    return ret
+
+
+def is_boolean(value: t.Any) -> TypeGuard[bool]:
+    """
+    Checks if `value` is a boolean value.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is a boolean.
+
+    Example:
+
+        >>> is_boolean(True)
+        True
+        >>> is_boolean(False)
+        True
+        >>> is_boolean(0)
+        False
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 3.0.0
+        Added ``is_bool`` as alias.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``is_bool``.
+    """
+    return isinstance(value, bool)
+
+
+def is_builtin(value: t.Any) -> bool:
+    """
+    Checks if `value` is a Python builtin function or method.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is a Python builtin function or method.
+
+    Example:
+
+        >>> is_builtin(1)
+        True
+        >>> is_builtin(list)
+        True
+        >>> is_builtin("foo")
+        False
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``is_native``.
+    """
+    try:
+        return isinstance(value, BuiltinFunctionType) or value in BUILTINS
+    except TypeError:  # pragma: no cover
+        return False
+
+
+def is_date(value: t.Any) -> bool:
+    """
+    Check if `value` is a date object.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is a date object.
+
+    Example:
+
+        >>> import datetime
+        >>> is_date(datetime.date.today())
+        True
+        >>> is_date(datetime.datetime.today())
+        True
+        >>> is_date("2014-01-01")
+        False
+
+    Note:
+        This will also return ``True`` for datetime objects.
+
+    .. versionadded:: 1.0.0
+    """
+    return isinstance(value, datetime.date)
+
+
+def is_decreasing(
+    value: t.Union["SupportsRichComparison", t.List["SupportsRichComparison"]],
+) -> bool:
+    """
+    Check if `value` is monotonically decreasing.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is monotonically decreasing.
+
+    Example:
+
+        >>> is_decreasing([5, 4, 4, 3])
+        True
+        >>> is_decreasing([5, 5, 5])
+        True
+        >>> is_decreasing([5, 4, 5])
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return is_monotone(value, operator.ge)  # type: ignore
+
+
+def is_dict(value: t.Any) -> bool:
+    """
+    Checks if `value` is a ``dict``.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is a ``dict``.
+
+    Example:
+
+        >>> is_dict({})
+        True
+        >>> is_dict([])
+        False
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 3.0.0
+        Added :func:`is_dict` as main definition and made `is_plain_object`` an alias.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``is_plain_object``.
+    """
+    return isinstance(value, dict)
+
+
+def is_empty(value: t.Any) -> bool:
+    """
+    Checks if `value` is empty.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is empty.
+
+    Example:
+
+        >>> is_empty(0)
+        True
+        >>> is_empty(1)
+        True
+        >>> is_empty(True)
+        True
+        >>> is_empty("foo")
+        False
+        >>> is_empty(None)
+        True
+        >>> is_empty({})
+        True
+
+    Note:
+        Returns ``True`` for booleans and numbers.
+
+    .. versionadded:: 1.0.0
+    """
+    return is_boolean(value) or is_number(value) or not value
+
+
+def is_equal(value: t.Any, other: t.Any) -> bool:
+    """
+    Performs a comparison between two values to determine if they are equivalent to each other.
+
+    Args:
+        value: Object to compare.
+        other: Object to compare.
+
+    Returns:
+        Whether `value` and `other` are equal.
+
+    Example:
+
+        >>> is_equal([1, 2, 3], [1, 2, 3])
+        True
+        >>> is_equal("a", "A")
+        False
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed :attr:`iteratee` from :func:`is_equal` and added it in
+        :func:`is_equal_with`.
+    """
+    return is_equal_with(value, other, customizer=None)
+
+
+def is_equal_cmp(other: T) -> t.Callable[[T], bool]:
+    """
+    Curried version of :func:`is_equal`.
+
+    Args:
+        other: Value to compare.
+
+    Returns:
+        A predicate checking whether passed :attr:`value` is equal to :attr:`other`.
+
+    Example:
+
+        >>> is_equal_cmp([1, 2, 3])([1, 2, 3])
+        True
+        >>> is_equal_cmp("a")("A")
+        False
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda value: is_equal(value, other)
+
+
+@t.overload
+def is_equal_with(value: T, other: T2, customizer: t.Callable[[T, T2], T3]) -> T3: ...
+
+
+@t.overload
+def is_equal_with(value: t.Any, other: t.Any, customizer: t.Callable[..., t.Any]) -> bool: ...
+
+
+@t.overload
+def is_equal_with(value: t.Any, other: t.Any, customizer: None) -> bool: ...
+
+
+def is_equal_with(value, other, customizer):
+    """
+    This method is like :func:`is_equal` except that it accepts customizer which is invoked to
+    compare values. A customizer is provided which will be executed to compare values. If the
+    customizer returns ``None``, comparisons will be handled by the method instead. The customizer
+    is invoked with two arguments: ``(value, other)``.
+
+    Args:
+        value: Object to compare.
+        other: Object to compare.
+        customizer: Customizer used to compare values from `value` and `other`.
+
+    Returns:
+        Whether `value` and `other` are equal.
+
+    Example:
+
+        >>> is_equal_with([1, 2, 3], [1, 2, 3], None)
+        True
+        >>> is_equal_with("a", "A", None)
+        False
+        >>> is_equal_with("a", "A", lambda a, b: a.lower() == b.lower())
+        True
+
+    .. versionadded:: 4.0.0
+    """
+    # If customizer provided, use it for comparison.
+    equal = customizer(value, other) if callable(customizer) else None
+
+    # Return customizer results if anything but None.
+    if equal is not None:
+        pass
+    elif (
+        callable(customizer)
+        and type(value) is type(other)
+        and isinstance(value, (list, dict))
+        and isinstance(other, (list, dict))
+        and len(value) == len(other)
+    ):
+        # Walk a/b to determine equality using customizer.
+        for key, val in iterator(value):
+            if pyd.has(other, key):
+                equal = is_equal_with(val, other[key], customizer)
+            else:
+                equal = False
+
+            if not equal:
+                break
+    else:
+        # Use basic == comparison.
+        equal = value == other
+
+    return equal
+
+
+def is_equal_with_cmp(other: T, customizer: t.Callable[[T, T], T3]) -> t.Callable[[T], T3]:
+    """
+    Curried version of :func:`is_equal_with`.
+
+    Args:
+        other: Value to compare.
+        customizer: Customizer used to compare values from `value` and `other`.
+
+    Returns:
+        A predicate checking whether passed :attr:`value` and :attr:`other` are equal.
+
+    Example:
+
+        >>> is_equal_with_cmp([1, 2, 3], None)([1, 2, 3])
+        True
+        >>> is_equal_with_cmp("a", None)("A")
+        False
+        >>> is_equal_with_cmp("a", lambda a, b: a.lower() == b.lower())("A")
+        True
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda value: is_equal_with(value, other, customizer)
+
+
+def is_error(value: t.Any) -> bool:
+    """
+    Checks if `value` is an ``Exception``.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is an exception.
+
+    Example:
+
+        >>> is_error(Exception())
+        True
+        >>> is_error(Exception)
+        False
+        >>> is_error(None)
+        False
+
+    .. versionadded:: 1.1.0
+    """
+    return isinstance(value, Exception)
+
+
+def is_even(value: t.Any) -> bool:
+    """
+    Checks if `value` is even.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is even.
+
+    Example:
+
+        >>> is_even(2)
+        True
+        >>> is_even(3)
+        False
+        >>> is_even(False)
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return is_number(value) and value % 2 == 0
+
+
+def is_float(value: t.Any) -> TypeGuard[float]:
+    """
+    Checks if `value` is a float.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is a float.
+
+    Example:
+
+        >>> is_float(1.0)
+        True
+        >>> is_float(1)
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return isinstance(value, float)
+
+
+def is_function(value: t.Any) -> bool:
+    """
+    Checks if `value` is a function.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is callable.
+
+    Example:
+
+        >>> is_function(list)
+        True
+        >>> is_function(lambda: True)
+        True
+        >>> is_function(1)
+        False
+
+    .. versionadded:: 1.0.0
+    """
+    return callable(value)
+
+
+def is_increasing(
+    value: t.Union["SupportsRichComparison", t.List["SupportsRichComparison"]],
+) -> bool:
+    """
+    Check if `value` is monotonically increasing.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is monotonically increasing.
+
+    Example:
+
+        >>> is_increasing([1, 3, 5])
+        True
+        >>> is_increasing([1, 1, 2, 3, 3])
+        True
+        >>> is_increasing([5, 5, 5])
+        True
+        >>> is_increasing([1, 2, 4, 3])
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return is_monotone(value, operator.le)  # type: ignore
+
+
+def is_indexed(value: t.Any) -> bool:
+    """
+    Checks if `value` is integer indexed, i.e., ``list``, ``str`` or ``tuple``.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is integer indexed.
+
+    Example:
+
+        >>> is_indexed("")
+        True
+        >>> is_indexed([])
+        True
+        >>> is_indexed(())
+        True
+        >>> is_indexed({})
+        False
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 3.0.0
+        Return ``True`` for tuples.
+    """
+    return isinstance(value, (list, tuple, str))
+
+
+def is_instance_of(value: t.Any, types: t.Union[type, t.Tuple[type, ...]]) -> bool:
+    """
+    Checks if `value` is an instance of `types`.
+
+    Args:
+        value: Value to check.
+        types: Types to check against. Pass as ``tuple`` to check if `value` is one of
+            multiple types.
+
+    Returns:
+        Whether `value` is an instance of `types`.
+
+    Example:
+
+        >>> is_instance_of({}, dict)
+        True
+        >>> is_instance_of({}, list)
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return isinstance(value, types)
+
+
+def is_instance_of_cmp(
+    types: t.Union[type, t.Tuple[type, ...]],
+) -> t.Callable[[t.Any], bool]:
+    """
+    Curried version of :func:`is_instance_of`.
+
+    Args:
+        types: Types to check against. Pass as ``tuple`` to check if `value` is one of
+            multiple types.
+
+    Returns:
+        A predicate checking whether passed :attr:`value` is an instance of :attr:`types`.
+
+    Example:
+
+        >>> is_instance_of_cmp(dict)({})
+        True
+        >>> is_instance_of_cmp(list)({})
+        False
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda value: is_instance_of(value, types)
+
+
+def is_integer(value: t.Any) -> TypeGuard[int]:
+    """
+    Checks if `value` is a integer.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is an integer.
+
+    Example:
+
+        >>> is_integer(1)
+        True
+        >>> is_integer(1.0)
+        False
+        >>> is_integer(True)
+        False
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 3.0.0
+        Added ``is_int`` as alias.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``is_int``.
+    """
+    return is_number(value) and isinstance(value, int)
+
+
+def is_iterable(value: t.Any) -> bool:
+    """
+    Checks if `value` is an iterable.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is an iterable.
+
+    Example:
+
+        >>> is_iterable([])
+        True
+        >>> is_iterable({})
+        True
+        >>> is_iterable(())
+        True
+        >>> is_iterable(5)
+        False
+        >>> is_iterable(True)
+        False
+
+    .. versionadded:: 3.3.0
+    """
+    try:
+        iter(value)
+    except TypeError:
+        return False
+    else:
+        return True
+
+
+def is_json(value: t.Any) -> bool:
+    """
+    Checks if `value` is a valid JSON string.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is JSON.
+
+    Example:
+
+        >>> is_json({})
+        False
+        >>> is_json("{}")
+        True
+        >>> is_json({"hello": 1, "world": 2})
+        False
+        >>> is_json('{"hello": 1, "world": 2}')
+        True
+
+    .. versionadded:: 2.0.0
+    """
+    try:
+        json.loads(value)
+        return True
+    except Exception:
+        return False
+
+
+def is_list(value: t.Any) -> bool:
+    """
+    Checks if `value` is a list.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is a list.
+
+    Example:
+
+        >>> is_list([])
+        True
+        >>> is_list({})
+        False
+        >>> is_list(())
+        False
+
+    .. versionadded:: 1.0.0
+    """
+    return isinstance(value, list)
+
+
+def is_match(obj: t.Any, source: t.Any) -> bool:
+    """
+    Performs a partial deep comparison between `obj` and `source` to determine if `obj` contains
+    equivalent property values.
+
+    Args:
+        obj: Object to compare.
+        source: Object of property values to match.
+
+    Returns:
+        Whether `obj` is a match or not.
+
+    Example:
+
+        >>> is_match({'a': 1, 'b': 2}, {'b': 2})
+        True
+        >>> is_match({'a': 1, 'b': 2}, {'b': 3})
+        False
+        >>> is_match({'a': [{'b': [{'c': 3, 'd': 4}]}]},\
+                     {'a': [{'b': [{'d': 4}]}]})
+        True
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 3.2.0
+        Don't compare `obj` and `source` using ``type``. Use ``isinstance``
+        exclusively.
+
+    .. versionchanged:: 4.0.0
+        Move `iteratee` argument to :func:`is_match_with`.
+    """
+    return is_match_with(obj, source)
+
+
+def is_match_cmp(source: t.Any) -> t.Callable[[t.Any], bool]:
+    """
+    Curried version of :func:`is_match`.
+
+    Args:
+        source: Object of property values to match.
+
+    Returns:
+        A predicate checking whether passed :attr:`obj` is a match or not.
+
+    Example:
+
+        >>> is_match_cmp({"b": 2})({"a": 1, "b": 2})
+        True
+        >>> is_match_cmp({"b": 3})({"a": 1, "b": 2})
+        False
+        >>> is_match_cmp({"a": [{"b": [{"d": 4}]}]})({"a": [{"b": [{"c": 3, "d": 4}]}]})
+        True
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda obj: is_match(obj, source)
+
+
+def is_match_with(
+    obj: t.Any,
+    source: t.Any,
+    customizer: t.Any = None,
+    _key: t.Any = UNSET,
+    _obj: t.Any = UNSET,
+    _source: t.Any = UNSET,
+) -> bool:
+    """
+    This method is like :func:`is_match` except that it accepts customizer which is invoked to
+    compare values. If customizer returns ``None``, comparisons are handled by the method instead.
+    The customizer is invoked with five arguments: ``(obj_value, src_value, index|key, obj,
+    source)``.
+
+    Args:
+        obj: Object to compare.
+        source: Object of property values to match.
+        customizer: Customizer used to compare values from `obj` and `source`.
+
+    Returns:
+        Whether `obj` is a match or not.
+
+    Example:
+
+        >>> is_greeting = lambda val: val in ("hello", "hi")
+        >>> customizer = lambda ov, sv: is_greeting(ov) and is_greeting(sv)
+        >>> obj = {"greeting": "hello"}
+        >>> src = {"greeting": "hi"}
+        >>> is_match_with(obj, src, customizer)
+        True
+
+    .. versionadded:: 4.0.0
+    """
+    if _obj is UNSET:
+        _obj = obj
+
+    if _source is UNSET:
+        _source = source
+
+    if not callable(customizer):
+
+        def cbk(obj_value, src_value):
+            return obj_value == src_value
+
+        # no attribute `_argcount`
+        cbk._argcount = 2  # type: ignore
+    else:
+        cbk = customizer
+
+    if isinstance(source, (Mapping, Iterable)) and not isinstance(source, str):
+        # Set equal to True if source is empty, otherwise, False and then allow deep comparison to
+        # determine equality.
+        equal = not source
+
+        # Walk a/b to determine equality.
+        for key, value in iterator(source):
+            try:
+                obj_value = base_get(obj, key)
+                equal = is_match_with(obj_value, value, cbk, _key=key, _obj=_obj, _source=_source)
+            except Exception:
+                equal = False
+
+            if not equal:
+                break
+    else:
+        equal = callit(cbk, obj, source, _key, _obj, _source)
+
+    return equal
+
+
+def is_match_with_cmp(source: t.Any, customizer: t.Any = None) -> t.Callable[[t.Any], bool]:
+    """
+    Curried version of :func:`is_match_with`.
+
+    Args:
+        source: Object of property values to match.
+        customizer: Customizer used to compare values from `obj` and `source`.
+
+    Returns:
+        A predicate checking whether passed :attr:`obj` is a match or not.
+
+    Example:
+
+        >>> is_greeting = lambda val: val in ("hello", "hi")
+        >>> customizer = lambda ov, sv: is_greeting(ov) and is_greeting(sv)
+        >>> obj = {"greeting": "hello"}
+        >>> src = {"greeting": "hi"}
+        >>> is_match_with_cmp(src, customizer)(obj)
+        True
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda obj: is_match_with(obj, source, customizer)
+
+
+def is_monotone(value: t.Union[T, t.List[T]], op: t.Callable[[T, T], t.Any]) -> bool:
+    """
+    Checks if `value` is monotonic when `operator` used for comparison.
+
+    Args:
+        value: Value to check.
+        op: Operation to used for comparison.
+
+    Returns:
+        Whether `value` is monotone.
+
+    Example:
+
+        >>> is_monotone([1, 1, 2, 3], operator.le)
+        True
+        >>> is_monotone([1, 1, 2, 3], operator.lt)
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    if not is_list(value):
+        l_value = [value]
+    else:
+        l_value = value  # type: ignore
+
+    search = (
+        False
+        for x, y in zip(l_value, islice(l_value, 1, None))
+        if not op(x, y)  # type: ignore
+    )
+
+    return next(search, True)
+
+
+def is_monotone_cmp(
+    op: t.Callable[[T, T], t.Any],
+) -> t.Callable[[t.Union[T, t.List[T]]], bool]:
+    """
+    Curried version of :func:`is_monotone`.
+
+    Args:
+        op: Operation to used for comparison.
+
+    Returns:
+        A predicate checking whether passed :attr:`value` is monotone.
+
+    Example:
+
+        >>> is_monotone_cmp(operator.le)([1, 1, 2, 3])
+        True
+        >>> is_monotone_cmp(operator.lt)([1, 1, 2, 3])
+        False
+
+    .. versionadded:: 7.1.0
+    """
+    return lambda value: is_monotone(value, op)
+
+
+def is_nan(value: t.Any) -> bool:
+    """
+    Checks if `value` is not a number.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is not a number.
+
+    Example:
+
+        >>> is_nan("a")
+        True
+        >>> is_nan(1)
+        False
+        >>> is_nan(1.0)
+        False
+
+    .. versionadded:: 1.0.0
+    """
+    return not is_number(value)
+
+
+def is_negative(value: t.Any) -> bool:
+    """
+    Checks if `value` is negative.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is negative.
+
+    Example:
+
+        >>> is_negative(-1)
+        True
+        >>> is_negative(0)
+        False
+        >>> is_negative(1)
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return is_number(value) and value < 0
+
+
+def is_none(value: t.Any) -> TypeGuard[None]:
+    """
+    Checks if `value` is `None`.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is ``None``.
+
+    Example:
+
+        >>> is_none(None)
+        True
+        >>> is_none(False)
+        False
+
+    .. versionadded:: 1.0.0
+    """
+    return value is None
+
+
+def is_number(value: t.Any) -> bool:
+    """
+    Checks if `value` is a number.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is a number.
+
+    Note:
+        Returns ``True`` for ``int``, ``long`` (PY2), ``float``, and
+        ``decimal.Decimal``.
+
+    Example:
+
+        >>> is_number(1)
+        True
+        >>> is_number(1.0)
+        True
+        >>> is_number("a")
+        False
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 3.0.0
+        Added ``is_num`` as alias.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``is_num``.
+    """
+    return not is_boolean(value) and isinstance(value, NUMBER_TYPES)
+
+
+def is_object(value: t.Any) -> bool:
+    """
+    Checks if `value` is a ``list`` or ``dict``.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is ``list`` or ``dict``.
+
+    Example:
+
+        >>> is_object([])
+        True
+        >>> is_object({})
+        True
+        >>> is_object(())
+        False
+        >>> is_object(1)
+        False
+
+    .. versionadded:: 1.0.0
+    """
+    return isinstance(value, (list, dict))
+
+
+def is_odd(value: t.Any) -> bool:
+    """
+    Checks if `value` is odd.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is odd.
+
+    Example:
+
+        >>> is_odd(3)
+        True
+        >>> is_odd(2)
+        False
+        >>> is_odd("a")
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return is_number(value) and value % 2 != 0
+
+
+def is_positive(value: t.Any) -> bool:
+    """
+    Checks if `value` is positive.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is positive.
+
+    Example:
+
+        >>> is_positive(1)
+        True
+        >>> is_positive(0)
+        False
+        >>> is_positive(-1)
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return is_number(value) and value > 0
+
+
+def is_reg_exp(value: t.Any) -> TypeGuard[re.Pattern[t.Any]]:
+    """
+    Checks if `value` is a ``RegExp`` object.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is a RegExp object.
+
+    Example:
+
+        >>> is_reg_exp(re.compile(""))
+        True
+        >>> is_reg_exp("")
+        False
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``is_re``.
+    """
+    return isinstance(value, RegExp)
+
+
+def is_set(value: t.Any) -> bool:
+    """
+    Checks if the given value is a set object or not.
+
+    Args:
+        value: Value passed in by the user.
+
+    Returns:
+        True if the given value is a set else False.
+
+    Example:
+
+        >>> is_set(set([1, 2]))
+        True
+        >>> is_set([1, 2, 3])
+        False
+
+    .. versionadded:: 4.0.0
+    """
+    return isinstance(value, set)
+
+
+def is_strictly_decreasing(
+    value: t.Union["SupportsRichComparison", t.List["SupportsRichComparison"]],
+) -> bool:
+    """
+    Check if `value` is strictly decreasing.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is strictly decreasing.
+
+    Example:
+
+        >>> is_strictly_decreasing([4, 3, 2, 1])
+        True
+        >>> is_strictly_decreasing([4, 4, 2, 1])
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return is_monotone(value, operator.gt)  # type: ignore
+
+
+def is_strictly_increasing(
+    value: t.Union["SupportsRichComparison", t.List["SupportsRichComparison"]],
+) -> bool:
+    """
+    Check if `value` is strictly increasing.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is strictly increasing.
+
+    Example:
+
+        >>> is_strictly_increasing([1, 2, 3, 4])
+        True
+        >>> is_strictly_increasing([1, 1, 3, 4])
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return is_monotone(value, operator.lt)  # type: ignore
+
+
+def is_string(value: t.Any) -> TypeGuard[str]:
+    """
+    Checks if `value` is a string.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is a string.
+
+    Example:
+
+        >>> is_string("")
+        True
+        >>> is_string(1)
+        False
+
+    .. versionadded:: 1.0.0
+    """
+    return isinstance(value, str)
+
+
+def is_tuple(value: t.Any) -> bool:
+    """
+    Checks if `value` is a tuple.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is a tuple.
+
+    Example:
+
+        >>> is_tuple(())
+        True
+        >>> is_tuple({})
+        False
+        >>> is_tuple([])
+        False
+
+    .. versionadded:: 3.0.0
+    """
+    return isinstance(value, tuple)
+
+
+def is_zero(value: t.Any) -> TypeGuard[int]:
+    """
+    Checks if `value` is ``0``.
+
+    Args:
+        value: Value to check.
+
+    Returns:
+        Whether `value` is ``0``.
+
+    Example:
+
+        >>> is_zero(0)
+        True
+        >>> is_zero(1)
+        False
+
+    .. versionadded:: 2.0.0
+    """
+    return value == 0 and is_integer(value)
diff --git a/.venv/lib/python3.12/site-packages/pydash/py.typed b/.venv/lib/python3.12/site-packages/pydash/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/py.typed
diff --git a/.venv/lib/python3.12/site-packages/pydash/strings.py b/.venv/lib/python3.12/site-packages/pydash/strings.py
new file mode 100644
index 00000000..40d2953e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/strings.py
@@ -0,0 +1,2363 @@
+"""
+String functions.
+
+.. versionadded:: 1.1.0
+"""
+
+from __future__ import annotations
+
+import html
+import math
+import re
+import typing
+import typing as t
+import unicodedata
+from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
+
+import pydash as pyd
+
+from .helpers import UNSET, Unset
+from .types import NumberT
+
+
+__all__ = (
+    "camel_case",
+    "capitalize",
+    "chop",
+    "chop_right",
+    "chars",
+    "clean",
+    "count_substr",
+    "deburr",
+    "decapitalize",
+    "ends_with",
+    "ensure_ends_with",
+    "ensure_starts_with",
+    "escape",
+    "escape_reg_exp",
+    "has_substr",
+    "human_case",
+    "insert_substr",
+    "join",
+    "kebab_case",
+    "lines",
+    "lower_case",
+    "lower_first",
+    "number_format",
+    "pad",
+    "pad_end",
+    "pad_start",
+    "pascal_case",
+    "predecessor",
+    "prune",
+    "quote",
+    "reg_exp_js_match",
+    "reg_exp_js_replace",
+    "reg_exp_replace",
+    "repeat",
+    "replace",
+    "replace_end",
+    "replace_start",
+    "separator_case",
+    "series_phrase",
+    "series_phrase_serial",
+    "slugify",
+    "snake_case",
+    "split",
+    "start_case",
+    "starts_with",
+    "strip_tags",
+    "substr_left",
+    "substr_left_end",
+    "substr_right",
+    "substr_right_end",
+    "successor",
+    "surround",
+    "swap_case",
+    "title_case",
+    "to_lower",
+    "to_upper",
+    "trim",
+    "trim_end",
+    "trim_start",
+    "truncate",
+    "unescape",
+    "unquote",
+    "upper_case",
+    "upper_first",
+    "url",
+    "words",
+)
+
+T = t.TypeVar("T")
+T2 = t.TypeVar("T2")
+
+
+class JSRegExp:
+    """
+    Javascript-style regular expression pattern.
+
+    Converts a Javascript-style regular expression to the equivalent Python version.
+    """
+
+    def __init__(self, reg_exp: str) -> None:
+        pattern, options = reg_exp[1:].rsplit("/", 1)
+
+        self._global = "g" in options
+        self._ignore_case = "i" in options
+
+        flags = re.I if self._ignore_case else 0
+        self.pattern = re.compile(pattern, flags=flags)
+
+    def find(self, text: str) -> t.List[str]:
+        """Return list of regular expression matches."""
+        if self._global:
+            results = self.pattern.findall(text)
+        else:
+            res = self.pattern.search(text)
+            if res:
+                results = [res.group()]
+            else:
+                results = []
+        return results
+
+    def replace(self, text: str, repl: t.Union[str, t.Callable[[re.Match[str]], str]]) -> str:
+        """Replace parts of text that match the regular expression."""
+        count = 0 if self._global else 1
+        return self.pattern.sub(repl, text, count=count)
+
+
+HTML_ESCAPES = {"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;", "`": "&#96;"}
+
+DEBURRED_LETTERS = {
+    "\xc0": "A",
+    "\xc1": "A",
+    "\xc2": "A",
+    "\xc3": "A",
+    "\xc4": "A",
+    "\xc5": "A",
+    "\xe0": "a",
+    "\xe1": "a",
+    "\xe2": "a",
+    "\xe3": "a",
+    "\xe4": "a",
+    "\xe5": "a",
+    "\xc7": "C",
+    "\xe7": "c",
+    "\xd0": "D",
+    "\xf0": "d",
+    "\xc8": "E",
+    "\xc9": "E",
+    "\xca": "E",
+    "\xcb": "E",
+    "\xe8": "e",
+    "\xe9": "e",
+    "\xea": "e",
+    "\xeb": "e",
+    "\xcc": "I",
+    "\xcd": "I",
+    "\xce": "I",
+    "\xcf": "I",
+    "\xec": "i",
+    "\xed": "i",
+    "\xee": "i",
+    "\xef": "i",
+    "\xd1": "N",
+    "\xf1": "n",
+    "\xd2": "O",
+    "\xd3": "O",
+    "\xd4": "O",
+    "\xd5": "O",
+    "\xd6": "O",
+    "\xd8": "O",
+    "\xf2": "o",
+    "\xf3": "o",
+    "\xf4": "o",
+    "\xf5": "o",
+    "\xf6": "o",
+    "\xf8": "o",
+    "\xd9": "U",
+    "\xda": "U",
+    "\xdb": "U",
+    "\xdc": "U",
+    "\xf9": "u",
+    "\xfa": "u",
+    "\xfb": "u",
+    "\xfc": "u",
+    "\xdd": "Y",
+    "\xfd": "y",
+    "\xff": "y",
+    "\xc6": "Ae",
+    "\xe6": "ae",
+    "\xde": "Th",
+    "\xfe": "th",
+    "\xdf": "ss",
+    "\xd7": " ",
+    "\xf7": " ",
+}
+
+# Use Javascript style regex to make Lo-Dash compatibility easier.
+# Lodash Regex definitions: https://github.com/lodash/lodash/blob/master/.internal/unicodeWords.js
+
+# References: https://github.com/lodash/lodash/blob/master/words.js#L8
+RS_ASCII_WORDS = "/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g"
+RS_LATIN1 = "/[\xc0-\xff]/g"
+
+# Used to compose unicode character classes.
+RS_ASTRAL_RANGE = "\\ud800-\\udfff"
+RS_COMBO_MARKS_RANGE = "\\u0300-\\u036f"
+RE_COMBO_HALF_MARKS_RANGE = "\\ufe20-\\ufe2f"
+RS_COMBO_SYMBOLS_RANGE = "\\u20d0-\\u20ff"
+RS_COMBO_MARKS_EXTENDED_RANGE = "\\u1ab0-\\u1aff"
+RS_COMBO_MARKS_SUPPLEMENT_RANGE = "\\u1dc0-\\u1dff"
+RS_COMBO_RANGE = (
+    RS_COMBO_MARKS_RANGE
+    + RE_COMBO_HALF_MARKS_RANGE
+    + RS_COMBO_SYMBOLS_RANGE
+    + RS_COMBO_MARKS_EXTENDED_RANGE
+    + RS_COMBO_MARKS_SUPPLEMENT_RANGE
+)
+RS_DINGBAT_RANGE = "\\u2700-\\u27bf"
+RS_LOWER_RANGE = "a-z\\xdf-\\xf6\\xf8-\\xff"
+RS_MATH_OP_RANGE = "\\xac\\xb1\\xd7\\xf7"
+RS_NON_CHAR_RANGE = "\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf"
+RS_PUNCTUATION_RANGE = "\\u2000-\\u206f"
+RS_SPACE_RANGE = (
+    " \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\"
+    "u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\"
+    "u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000"
+)
+RS_UPPER_RANGE = "A-Z\\xc0-\\xd6\\xd8-\\xde"
+RS_VAR_RANGE = "\\ufe0e\\ufe0f"
+RS_BREAK_RANGE = RS_MATH_OP_RANGE + RS_NON_CHAR_RANGE + RS_PUNCTUATION_RANGE + RS_SPACE_RANGE
+
+# Used to compose unicode capture groups.
+RS_APOS = "['\u2019]"
+RS_BREAK = f"[{RS_BREAK_RANGE}]"
+RS_COMBO = f"[{RS_COMBO_RANGE}]"
+RS_DIGIT = "\\d"
+RS_DINGBAT = f"[{RS_DINGBAT_RANGE}]"
+RS_LOWER = f"[{RS_LOWER_RANGE}]"
+RS_MISC = (
+    f"[^{RS_ASTRAL_RANGE}{RS_BREAK_RANGE}{RS_DIGIT}"
+    f"{RS_DINGBAT_RANGE}{RS_LOWER_RANGE}{RS_UPPER_RANGE}]"
+)
+RS_FITZ = "\\ud83c[\\udffb-\\udfff]"
+RS_MODIFIER = f"(?:{RS_COMBO}|{RS_FITZ})"
+RS_NON_ASTRAL = f"[^{RS_ASTRAL_RANGE}]"
+RS_REGIONAL = "(?:\\ud83c[\\udde6-\\uddff]){2}"
+RS_SURR_PAIR = "[\\ud800-\\udbff][\\udc00-\\udfff]"
+RS_UPPER = f"[{RS_UPPER_RANGE}]"
+RS_ZWJ = "\\u200d"
+
+# Used to compose unicode regexes.
+RS_MISC_LOWER = f"(?:{RS_LOWER}|{RS_MISC})"
+RS_MISC_UPPER = f"(?:{RS_UPPER}|{RS_MISC})"
+RS_OPT_CONTR_LOWER = f"(?:{RS_APOS}(?:d|ll|m|re|s|t|ve))?"
+RS_OPT_CONTR_UPPER = f"(?:{RS_APOS}(?:D|LL|M|RE|S|T|VE))?"
+RE_OPT_MOD = f"{RS_MODIFIER}?"
+RS_OPT_VAR = f"[{RS_VAR_RANGE}]?"
+RS_OPT_JOIN = (
+    f"(?:{RS_ZWJ}(?:{RS_NON_ASTRAL}|{RS_REGIONAL}|{RS_SURR_PAIR}){RS_OPT_VAR}{RE_OPT_MOD})*"
+)
+RS_ORD_LOWER = "\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])"
+RS_ORD_UPPER = "\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])"
+RS_SEQ = RS_OPT_VAR + RE_OPT_MOD + RS_OPT_JOIN
+RS_EMOJI = f"(?:{RS_DINGBAT}|{RS_REGIONAL}|{RS_SURR_PAIR}){RS_SEQ}"
+
+RS_HAS_UNICODE_WORD = "[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]"
+RS_UNICODE_WORDS = (
+    f"/"
+    f"{RS_UPPER}?{RS_LOWER}+{RS_OPT_CONTR_LOWER}(?={RS_BREAK}|{RS_UPPER}|$)"
+    f"|{RS_MISC_UPPER}+{RS_OPT_CONTR_UPPER}(?={RS_BREAK}|{RS_UPPER}{RS_MISC_LOWER}|$)"
+    f"|{RS_UPPER}?{RS_MISC_LOWER}+{RS_OPT_CONTR_LOWER}"
+    f"|{RS_UPPER}+{RS_OPT_CONTR_UPPER}"
+    f"|{RS_ORD_UPPER}"
+    f"|{RS_ORD_LOWER}"
+    f"|{RS_DIGIT}+"
+    f"|{RS_EMOJI}"
+    f"/g"
+)
+
+# Compiled regexes for use in functions.
+JS_RE_ASCII_WORDS = JSRegExp(RS_ASCII_WORDS)
+JS_RE_UNICODE_WORDS = JSRegExp(RS_UNICODE_WORDS)
+JS_RE_LATIN1 = JSRegExp(RS_LATIN1)
+RE_HAS_UNICODE_WORD = re.compile(RS_HAS_UNICODE_WORD)
+RE_APOS = re.compile(RS_APOS)
+RE_HTML_TAGS = re.compile(r"<\/?[^>]+>")
+
+
+def camel_case(text: t.Any) -> str:
+    """
+    Converts `text` to camel case.
+
+    Args:
+        text: String to convert.
+
+    Returns:
+        String converted to camel case.
+
+    Example:
+
+        >>> camel_case("FOO BAR_bAz")
+        'fooBarBAz'
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+    """
+    text = "".join(word.title() for word in compounder(text))
+    return text[:1].lower() + text[1:]
+
+
+def capitalize(text: t.Any, strict: bool = True) -> str:
+    """
+    Capitalizes the first character of `text`.
+
+    Args:
+        text: String to capitalize.
+        strict: Whether to cast rest of string to lower case. Defaults to ``True``.
+
+    Returns:
+        Capitalized string.
+
+    Example:
+
+        >>> capitalize("once upon a TIME")
+        'Once upon a time'
+        >>> capitalize("once upon a TIME", False)
+        'Once upon a TIME'
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 3.0.0
+        Added `strict` option.
+    """
+    text = pyd.to_string(text)
+    return text.capitalize() if strict else text[:1].upper() + text[1:]
+
+
+def chars(text: t.Any) -> t.List[str]:
+    """
+    Split `text` into a list of single characters.
+
+    Args:
+        text: String to split up.
+
+    Returns:
+        List of individual characters.
+
+    Example:
+
+        >>> chars("onetwo")
+        ['o', 'n', 'e', 't', 'w', 'o']
+
+    .. versionadded:: 3.0.0
+    """
+    return list(pyd.to_string(text))
+
+
+def chop(text: t.Any, step: int) -> t.List[str]:
+    """
+    Break up `text` into intervals of length `step`.
+
+    Args:
+        text: String to chop.
+        step: Interval to chop `text`.
+
+    Returns:
+        List of chopped characters. If `text` is `None` an empty list is returned.
+
+    Example:
+
+        >>> chop("abcdefg", 3)
+        ['abc', 'def', 'g']
+
+    .. versionadded:: 3.0.0
+    """
+    if text is None:
+        return []
+
+    text = pyd.to_string(text)
+
+    if step <= 0:
+        chopped = [text]
+    else:
+        chopped = [text[i : i + step] for i in range(0, len(text), step)]
+
+    return chopped
+
+
+def chop_right(text: t.Any, step: int) -> t.List[str]:
+    """
+    Like :func:`chop` except `text` is chopped from right.
+
+    Args:
+        text: String to chop.
+        step: Interval to chop `text`.
+
+    Returns:
+        List of chopped characters.
+
+    Example:
+
+        >>> chop_right("abcdefg", 3)
+        ['a', 'bcd', 'efg']
+
+    .. versionadded:: 3.0.0
+    """
+    if text is None:
+        return []
+
+    text = pyd.to_string(text)
+
+    if step <= 0:
+        chopped = [text]
+    else:
+        text_len = len(text)
+        chopped = [text[-(i + step) : text_len - i] for i in range(0, text_len, step)][::-1]
+
+    return chopped
+
+
+def clean(text: t.Any) -> str:
+    """
+    Trim and replace multiple spaces with a single space.
+
+    Args:
+        text: String to clean.
+
+    Returns:
+        Cleaned string.
+
+    Example:
+
+        >>> clean("a  b   c    d")
+        'a b c d'
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    return " ".join(pyd.compact(text.split()))
+
+
+def count_substr(text: t.Any, subtext: t.Any) -> int:
+    """
+    Count the occurrences of `subtext` in `text`.
+
+    Args:
+        text: Source string to count from.
+        subtext: String to count.
+
+    Returns:
+        Number of occurrences of `subtext` in `text`.
+
+    Example:
+
+        >>> count_substr("aabbccddaabbccdd", "bc")
+        2
+
+    .. versionadded:: 3.0.0
+    """
+    if text is None or subtext is None:
+        return 0
+
+    text = pyd.to_string(text)
+    subtext = pyd.to_string(subtext)
+
+    return text.count(subtext)
+
+
+def deburr(text: t.Any) -> str:
+    """
+    Deburrs `text` by converting latin-1 supplementary letters to basic latin letters.
+
+    Args:
+        text: String to deburr.
+
+    Returns:
+        Deburred string.
+
+    Example:
+
+        >>> deburr("déjà vu")
+        '...
+        >>> "deja vu"
+        'deja vu'
+
+    .. versionadded:: 2.0.0
+    """
+    text = pyd.to_string(text)
+    return JS_RE_LATIN1.replace(
+        text, lambda match: DEBURRED_LETTERS.get(match.group(), match.group())
+    )
+
+
+def decapitalize(text: t.Any) -> str:
+    """
+    Decaptitalizes the first character of `text`.
+
+    Args:
+        text: String to decapitalize.
+
+    Returns:
+        Decapitalized string.
+
+    Example:
+
+        >>> decapitalize("FOO BAR")
+        'fOO BAR'
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    return text[:1].lower() + text[1:]
+
+
+def ends_with(text: t.Any, target: t.Any, position: t.Union[int, None] = None) -> bool:
+    """
+    Checks if `text` ends with a given target string.
+
+    Args:
+        text: String to check.
+        target: String to check for.
+        position: Position to search from. Defaults to end of `text`.
+
+    Returns:
+        Whether `text` ends with `target`.
+
+    Example:
+
+        >>> ends_with("abc def", "def")
+        True
+        >>> ends_with("abc def", 4)
+        False
+
+    .. versionadded:: 1.1.0
+    """
+    target = pyd.to_string(target)
+    text = pyd.to_string(text)
+
+    if position is None:
+        position = len(text)
+
+    return text[:position].endswith(target)
+
+
+def ensure_ends_with(text: t.Any, suffix: t.Any) -> str:
+    """
+    Append a given suffix to a string, but only if the source string does not end with that suffix.
+
+    Args:
+        text: Source string to append `suffix` to.
+        suffix: String to append to the source string if the source string does not end with
+            `suffix`.
+
+    Returns:
+        source string possibly extended by `suffix`.
+
+    Example:
+
+        >>> ensure_ends_with("foo bar", "!")
+        'foo bar!'
+        >>> ensure_ends_with("foo bar!", "!")
+        'foo bar!'
+
+    .. versionadded:: 2.4.0
+    """
+    text = pyd.to_string(text)
+    suffix = pyd.to_string(suffix)
+    if text.endswith(suffix):
+        return text
+    return f"{text}{suffix}"
+
+
+def ensure_starts_with(text: t.Any, prefix: t.Any) -> str:
+    """
+    Prepend a given prefix to a string, but only if the source string does not start with that
+    prefix.
+
+    Args:
+        text: Source string to prepend `prefix` to.
+        prefix: String to prepend to the source string if the source string does not start
+            with `prefix`.
+
+    Returns:
+        source string possibly prefixed by `prefix`
+
+    Example:
+
+        >>> ensure_starts_with("foo bar", "Oh my! ")
+        'Oh my! foo bar'
+        >>> ensure_starts_with("Oh my! foo bar", "Oh my! ")
+        'Oh my! foo bar'
+
+    .. versionadded:: 2.4.0
+    """
+    text = pyd.to_string(text)
+    prefix = pyd.to_string(prefix)
+    if text.startswith(prefix):
+        return text
+    return f"{prefix}{text}"
+
+
+def escape(text: t.Any) -> str:
+    r"""
+    Converts the characters ``&``, ``<``, ``>``, ``"``, ``'``, and ``\``` in `text` to their
+    corresponding HTML entities.
+
+    Args:
+        text: String to escape.
+
+    Returns:
+        HTML escaped string.
+
+    Example:
+
+        >>> escape('"1 > 2 && 3 < 4"')
+        '&quot;1 &gt; 2 &amp;&amp; 3 &lt; 4&quot;'
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 1.1.0
+        Moved function to :mod:`pydash.strings`.
+    """
+    text = pyd.to_string(text)
+    # NOTE: Not using html.escape because Lo-Dash escapes certain chars differently (e.g. `'` isn't
+    # escaped by html.escape() but is by Lo-Dash).
+    return "".join(HTML_ESCAPES.get(char, char) for char in text)
+
+
+def escape_reg_exp(text: t.Any) -> str:
+    """
+    Escapes the RegExp special characters in `text`.
+
+    Args:
+        text: String to escape.
+
+    Returns:
+        RegExp escaped string.
+
+    Example:
+
+        >>> escape_reg_exp("[()]")
+        '\\\\[\\\\(\\\\)\\\\]'
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``escape_re``
+    """
+    text = pyd.to_string(text)
+    return re.escape(text)
+
+
+def has_substr(text: t.Any, subtext: t.Any) -> bool:
+    """
+    Returns whether `subtext` is included in `text`.
+
+    Args:
+        text: String to search.
+        subtext: String to search for.
+
+    Returns:
+        Whether `subtext` is found in `text`.
+
+    Example:
+
+        >>> has_substr("abcdef", "bc")
+        True
+        >>> has_substr("abcdef", "bb")
+        False
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    subtext = pyd.to_string(subtext)
+    return text.find(subtext) >= 0
+
+
+def human_case(text: t.Any) -> str:
+    """
+    Converts `text` to human case which has only the first letter capitalized and each word
+    separated by a space.
+
+    Args:
+        text: String to convert.
+
+    Returns:
+        String converted to human case.
+
+    Example:
+
+        >>> human_case("abc-def_hij lmn")
+        'Abc def hij lmn'
+        >>> human_case("user_id")
+        'User'
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+    """
+    return (
+        pyd.chain(text)
+        .snake_case()
+        .reg_exp_replace("_id$", "")
+        .replace("_", " ")
+        .capitalize()
+        .value()
+    )
+
+
+def insert_substr(text: t.Any, index: int, subtext: t.Any) -> str:
+    """
+    Insert `subtext` in `text` starting at position `index`.
+
+    Args:
+        text: String to add substring to.
+        index: String index to insert into.
+        subtext: String to insert.
+
+    Returns:
+        Modified string.
+
+    Example:
+
+        >>> insert_substr("abcdef", 3, "--")
+        'abc--def'
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    subtext = pyd.to_string(subtext)
+    return text[:index] + subtext + text[index:]
+
+
+def join(array: t.Iterable[t.Any], separator: t.Any = "") -> str:
+    """
+    Joins an iterable into a string using `separator` between each element.
+
+    Args:
+        array: Iterable to implode.
+        separator: Separator to using when joining. Defaults to ``''``.
+
+    Returns:
+        Joined string.
+
+    Example:
+
+        >>> join(["a", "b", "c"]) == "abc"
+        True
+        >>> join([1, 2, 3, 4], "&") == "1&2&3&4"
+        True
+        >>> join("abcdef", "-") == "a-b-c-d-e-f"
+        True
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``implode``.
+    """
+    return pyd.to_string(separator).join(pyd.map_(array or (), pyd.to_string))
+
+
+def kebab_case(text: t.Any) -> str:
+    """
+    Converts `text` to kebab case (a.k.a. spinal case).
+
+    Args:
+        text: String to convert.
+
+    Returns:
+        String converted to kebab case.
+
+    Example:
+
+        >>> kebab_case("a b c_d-e!f")
+        'a-b-c-d-e-f'
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+    """
+    return "-".join(word.lower() for word in compounder(text) if word)
+
+
+def lines(text: t.Any) -> t.List[str]:
+    r"""
+    Split lines in `text` into an array.
+
+    Args:
+        text: String to split.
+
+    Returns:
+        String split by lines.
+
+    Example:
+
+        >>> lines("a\nb\r\nc")
+        ['a', 'b', 'c']
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    return text.splitlines()
+
+
+def lower_case(text: t.Any) -> str:
+    """
+    Converts string to lower case as space separated words.
+
+    Args:
+        text: String to convert.
+
+    Returns:
+        String converted to lower case as space separated words.
+
+    Example:
+
+        >>> lower_case("fooBar")
+        'foo bar'
+        >>> lower_case("--foo-Bar--")
+        'foo bar'
+        >>> lower_case('/?*Foo10/;"B*Ar')
+        'foo 10 b ar'
+
+    .. versionadded:: 4.0.0
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+    """
+    return " ".join(compounder(text)).lower()
+
+
+def lower_first(text: str) -> str:
+    """
+    Converts the first character of string to lower case.
+
+    Args:
+        text: String passed in by the user.
+
+    Returns:
+        String in which the first character is converted to lower case.
+
+    Example:
+
+        >>> lower_first("FRED")
+        'fRED'
+        >>> lower_first("Foo Bar")
+        'foo Bar'
+        >>> lower_first("1foobar")
+        '1foobar'
+        >>> lower_first(";foobar")
+        ';foobar'
+
+    .. versionadded:: 4.0.0
+    """
+    return text[:1].lower() + text[1:]
+
+
+def number_format(
+    number: NumberT, scale: int = 0, decimal_separator: str = ".", order_separator: str = ","
+) -> str:
+    """
+    Format a number to scale with custom decimal and order separators.
+
+    Args:
+        number: Number to format.
+        scale: Number of decimals to include. Defaults to ``0``.
+        decimal_separator: Decimal separator to use. Defaults to ``'.'``.
+        order_separator: Order separator to use. Defaults to ``','``.
+
+    Returns:
+        Number formatted as string.
+
+    Example:
+
+        >>> number_format(1234.5678)
+        '1,235'
+        >>> number_format(1234.5678, 2, ",", ".")
+        '1.234,57'
+
+    .. versionadded:: 3.0.0
+    """
+    # Create a string formatter which converts number to the appropriately scaled representation.
+    fmt = f"{{0:.{scale:d}f}}"
+
+    try:
+        num_parts = fmt.format(number).split(".")
+    except ValueError:
+        text = ""
+    else:
+        int_part = num_parts[0]
+        dec_part = (num_parts + [""])[1]
+
+        # Reverse the integer part, chop it into groups of 3, join on `order_separator`, and then
+        # un-reverse the string.
+        int_part = order_separator.join(chop(int_part[::-1], 3))[::-1]
+
+        text = decimal_separator.join(pyd.compact([int_part, dec_part]))
+
+    return text
+
+
+def pad(text: t.Any, length: int, chars: t.Any = " ") -> str:
+    """
+    Pads `text` on the left and right sides if it is shorter than the given padding length. The
+    `chars` string may be truncated if the number of padding characters can't be evenly divided by
+    the padding length.
+
+    Args:
+        text: String to pad.
+        length: Amount to pad.
+        chars: Characters to pad with. Defaults to ``" "``.
+
+    Returns:
+        Padded string.
+
+    Example:
+
+        >>> pad("abc", 5)
+        ' abc '
+        >>> pad("abc", 6, "x")
+        'xabcxx'
+        >>> pad("abc", 5, "...")
+        '.abc.'
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 3.0.0
+        Fix handling of multiple `chars` so that padded string isn't over padded.
+    """
+    # pylint: disable=redefined-outer-name
+    text = pyd.to_string(text)
+    text_len = len(text)
+
+    if text_len >= length:
+        return text
+
+    mid = (length - text_len) / 2.0
+    left_len = int(math.floor(mid))
+    right_len = int(math.ceil(mid))
+    chars = pad_end("", right_len, chars)
+
+    return chars[:left_len] + text + chars
+
+
+def pad_end(text: t.Any, length: int, chars: t.Any = " ") -> str:
+    """
+    Pads `text` on the right side if it is shorter than the given padding length. The `chars` string
+    may be truncated if the number of padding characters can't be evenly divided by the padding
+    length.
+
+    Args:
+        text: String to pad.
+        length: Amount to pad.
+        chars: Characters to pad with. Defaults to ``" "``.
+
+    Returns:
+        Padded string.
+
+    Example:
+
+        >>> pad_end("abc", 5)
+        'abc  '
+        >>> pad_end("abc", 5, ".")
+        'abc..'
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``pad_right`` to ``pad_end``.
+    """
+    # pylint: disable=redefined-outer-name
+    text = pyd.to_string(text)
+    length = max((length, len(text)))
+    return (text + repeat(chars, length))[:length]
+
+
+def pad_start(text: t.Any, length: int, chars: t.Any = " ") -> str:
+    """
+    Pads `text` on the left side if it is shorter than the given padding length. The `chars` string
+    may be truncated if the number of padding characters can't be evenly divided by the padding
+    length.
+
+    Args:
+        text: String to pad.
+        length: Amount to pad.
+        chars: Characters to pad with. Defaults to ``" "``.
+
+    Returns:
+        Padded string.
+
+    Example:
+
+        >>> pad_start("abc", 5)
+        '  abc'
+        >>> pad_start("abc", 5, ".")
+        '..abc'
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``pad_left`` to ``pad_start``.
+    """
+    # pylint: disable=redefined-outer-name
+    text = pyd.to_string(text)
+    length = max(length, len(text))
+    return (repeat(chars, length) + text)[-length:]
+
+
+def pascal_case(text: t.Any, strict: bool = True) -> str:
+    """
+    Like :func:`camel_case` except the first letter is capitalized.
+
+    Args:
+        text: String to convert.
+        strict: Whether to cast rest of string to lower case. Defaults to ``True``.
+
+    Returns:
+        String converted to class case.
+
+    Example:
+
+        >>> pascal_case("FOO BAR_bAz")
+        'FooBarBaz'
+        >>> pascal_case("FOO BAR_bAz", False)
+        'FooBarBAz'
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+    """
+    text = pyd.to_string(text)
+
+    if strict:
+        text = text.lower()
+
+    return capitalize(camel_case(text), strict=False)
+
+
+def predecessor(char: t.Any) -> str:
+    """
+    Return the predecessor character of `char`.
+
+    Args:
+        char: Character to find the predecessor of.
+
+    Returns:
+        Predecessor character.
+
+    Example:
+
+        >>> predecessor("c")
+        'b'
+        >>> predecessor("C")
+        'B'
+        >>> predecessor("3")
+        '2'
+
+    .. versionadded:: 3.0.0
+    """
+    char = pyd.to_string(char)
+    return chr(ord(char) - 1)
+
+
+def prune(text: t.Any, length: int = 0, omission: str = "...") -> str:
+    """
+    Like :func:`truncate` except it ensures that the pruned string doesn't exceed the original
+    length, i.e., it avoids half-chopped words when truncating. If the pruned text + `omission` text
+    is longer than the original text, then the original text is returned.
+
+    Args:
+        text: String to prune.
+        length: Target prune length. Defaults to ``0``.
+        omission: Omission text to append to the end of the pruned string. Defaults
+            to ``'...'``.
+
+    Returns:
+        Pruned string.
+
+    Example:
+
+        >>> prune("Fe fi fo fum", 5)
+        'Fe fi...'
+        >>> prune("Fe fi fo fum", 6)
+        'Fe fi...'
+        >>> prune("Fe fi fo fum", 7)
+        'Fe fi...'
+        >>> prune("Fe fi fo fum", 8, ",,,")
+        'Fe fi fo,,,'
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    text_len = len(text)
+    omission_len = len(omission)
+
+    if text_len <= length:
+        return text
+
+    # Replace non-alphanumeric chars with whitespace.
+    def repl(match):
+        char = match.group(0)
+        return " " if char.upper() == char.lower() else char
+
+    subtext = reg_exp_replace(text[: length + 1], r".(?=\W*\w*$)", repl)
+
+    if re.match(r"\w\w", subtext[-2:]):
+        # Last two characters are alphanumeric. Remove last "word" from end of string so that we
+        # prune to the next whole word.
+        subtext = reg_exp_replace(subtext, r"\s*\S+$", "")
+    else:
+        # Last character (at least) is whitespace. So remove that character as well as any other
+        # whitespace.
+        subtext = subtext[:-1].rstrip()
+
+    subtext_len = len(subtext)
+
+    # Only add omission text if doing so will result in a string that is equal to or smaller than
+    # the original.
+    if (subtext_len + omission_len) <= text_len:
+        text = text[:subtext_len] + omission
+
+    return text
+
+
+def quote(text: t.Any, quote_char: t.Any = '"') -> str:
+    """
+    Quote a string with another string.
+
+    Args:
+        text: String to be quoted.
+        quote_char: the quote character. Defaults to ``'"'``.
+
+    Returns:
+        the quoted string.
+
+    Example:
+
+        >>> quote("To be or not to be")
+        '"To be or not to be"'
+        >>> quote("To be or not to be", "'")
+        "'To be or not to be'"
+
+    .. versionadded:: 2.4.0
+    """
+    return surround(text, quote_char)
+
+
+def reg_exp_js_match(text: t.Any, reg_exp: str) -> t.List[str]:
+    """
+    Return list of matches using Javascript style regular expression.
+
+    Args:
+        text: String to evaluate.
+        reg_exp: Javascript style regular expression.
+
+    Returns:
+        List of matches.
+
+    Example:
+
+        >>> reg_exp_js_match("aaBBcc", "/bb/")
+        []
+        >>> reg_exp_js_match("aaBBcc", "/bb/i")
+        ['BB']
+        >>> reg_exp_js_match("aaBBccbb", "/bb/i")
+        ['BB']
+        >>> reg_exp_js_match("aaBBccbb", "/bb/gi")
+        ['BB', 'bb']
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 3.0.0
+        Reordered arguments to make `text` first.
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``js_match`` to ``reg_exp_js_match``.
+    """
+    text = pyd.to_string(text)
+    return JSRegExp(reg_exp).find(text)
+
+
+def reg_exp_js_replace(
+    text: t.Any, reg_exp: str, repl: t.Union[str, t.Callable[[re.Match[str]], str]]
+) -> str:
+    """
+    Replace `text` with `repl` using Javascript style regular expression to find matches.
+
+    Args:
+        text: String to evaluate.
+        reg_exp: Javascript style regular expression.
+        repl: Replacement string or callable.
+
+    Returns:
+        Modified string.
+
+    Example:
+
+        >>> reg_exp_js_replace("aaBBcc", "/bb/", "X")
+        'aaBBcc'
+        >>> reg_exp_js_replace("aaBBcc", "/bb/i", "X")
+        'aaXcc'
+        >>> reg_exp_js_replace("aaBBccbb", "/bb/i", "X")
+        'aaXccbb'
+        >>> reg_exp_js_replace("aaBBccbb", "/bb/gi", "X")
+        'aaXccX'
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 3.0.0
+        Reordered arguments to make `text` first.
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``js_replace`` to ``reg_exp_js_replace``.
+    """
+    text = pyd.to_string(text)
+    if not pyd.is_function(repl):
+        repl = pyd.to_string(repl)
+    return JSRegExp(reg_exp).replace(text, repl)
+
+
+def reg_exp_replace(
+    text: t.Any,
+    pattern: t.Any,
+    repl: t.Union[str, t.Callable[[re.Match[str]], str]],
+    ignore_case: bool = False,
+    count: int = 0,
+) -> str:
+    """
+    Replace occurrences of regex `pattern` with `repl` in `text`. Optionally, ignore case when
+    replacing. Optionally, set `count` to limit number of replacements.
+
+    Args:
+        text: String to replace.
+        pattern: Pattern to find and replace.
+        repl: String to substitute `pattern` with.
+        ignore_case: Whether to ignore case when replacing. Defaults to ``False``.
+        count: Maximum number of occurrences to replace. Defaults to ``0`` which
+            replaces all.
+
+    Returns:
+        Replaced string.
+
+    Example:
+
+        >>> reg_exp_replace("aabbcc", "b", "X")
+        'aaXXcc'
+        >>> reg_exp_replace("aabbcc", "B", "X", ignore_case=True)
+        'aaXXcc'
+        >>> reg_exp_replace("aabbcc", "b", "X", count=1)
+        'aaXbcc'
+        >>> reg_exp_replace("aabbcc", "[ab]", "X")
+        'XXXXcc'
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``re_replace`` to ``reg_exp_replace``.
+    """
+    if pattern is None:
+        return pyd.to_string(text)
+
+    return replace(text, pattern, repl, ignore_case=ignore_case, count=count, escape=False)
+
+
+def repeat(text: t.Any, n: t.SupportsInt = 0) -> str:
+    """
+    Repeats the given string `n` times.
+
+    Args:
+        text: String to repeat.
+        n: Number of times to repeat the string.
+
+    Returns:
+        Repeated string.
+
+    Example:
+
+        >>> repeat(".", 5)
+        '.....'
+
+    .. versionadded:: 1.1.0
+    """
+    return pyd.to_string(text) * int(n)
+
+
+def replace(
+    text: t.Any,
+    pattern: t.Any,
+    repl: t.Union[str, t.Callable[[re.Match[str]], str]],
+    ignore_case: bool = False,
+    count: int = 0,
+    escape: bool = True,
+    from_start: bool = False,
+    from_end: bool = False,
+) -> str:
+    """
+    Replace occurrences of `pattern` with `repl` in `text`. Optionally, ignore case when replacing.
+    Optionally, set `count` to limit number of replacements.
+
+    Args:
+        text: String to replace.
+        pattern: Pattern to find and replace.
+        repl: String to substitute `pattern` with.
+        ignore_case: Whether to ignore case when replacing. Defaults to ``False``.
+        count: Maximum number of occurrences to replace. Defaults to ``0`` which
+            replaces all.
+        escape: Whether to escape `pattern` when searching. This is needed if a
+            literal replacement is desired when `pattern` may contain special regular expression
+            characters. Defaults to ``True``.
+        from_start: Whether to limit replacement to start of string.
+        from_end: Whether to limit replacement to end of string.
+
+    Returns:
+        Replaced string.
+
+    Example:
+
+        >>> replace("aabbcc", "b", "X")
+        'aaXXcc'
+        >>> replace("aabbcc", "B", "X", ignore_case=True)
+        'aaXXcc'
+        >>> replace("aabbcc", "b", "X", count=1)
+        'aaXbcc'
+        >>> replace("aabbcc", "[ab]", "X")
+        'aabbcc'
+        >>> replace("aabbcc", "[ab]", "X", escape=False)
+        'XXXXcc'
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 4.1.0
+        Added ``from_start`` and ``from_end`` arguments.
+
+    .. versionchanged:: 5.0.0
+        Added support for ``pattern`` as ``typing.Pattern`` object.
+    """
+    text = pyd.to_string(text)
+
+    if pattern is None:
+        return text
+
+    if not pyd.is_function(repl):
+        repl = pyd.to_string(repl)
+
+    flags = re.IGNORECASE if ignore_case else 0
+
+    if isinstance(pattern, typing.Pattern):
+        pat = pattern
+    else:
+        pattern = pyd.to_string(pattern)
+
+        if escape:
+            pattern = re.escape(pattern)
+
+        if from_start and not pattern.startswith("^"):
+            pattern = "^" + pattern
+
+        if from_end and not pattern.endswith("$"):
+            pattern += "$"
+
+        pat = re.compile(pattern, flags=flags)
+
+    return pat.sub(repl, text, count=count)
+
+
+def replace_end(
+    text: t.Any,
+    pattern: t.Any,
+    repl: t.Union[str, t.Callable[[re.Match[str]], str]],
+    ignore_case: bool = False,
+    escape: bool = True,
+) -> str:
+    """
+    Like :func:`replace` except it only replaces `text` with `repl` if `pattern` mathces the end of
+    `text`.
+
+    Args:
+        text: String to replace.
+        pattern: Pattern to find and replace.
+        repl: String to substitute `pattern` with.
+        ignore_case: Whether to ignore case when replacing. Defaults to ``False``.
+        escape: Whether to escape `pattern` when searching. This is needed if a
+            literal replacement is desired when `pattern` may contain special regular expression
+            characters. Defaults to ``True``.
+
+    Returns:
+        Replaced string.
+
+    Example:
+
+        >>> replace_end("aabbcc", "b", "X")
+        'aabbcc'
+        >>> replace_end("aabbcc", "c", "X")
+        'aabbcX'
+
+    .. versionadded:: 4.1.0
+    """
+    return replace(text, pattern, repl, ignore_case=ignore_case, escape=escape, from_end=True)
+
+
+def replace_start(
+    text: t.Any,
+    pattern: t.Any,
+    repl: t.Union[str, t.Callable[[re.Match[str]], str]],
+    ignore_case: bool = False,
+    escape: bool = True,
+) -> str:
+    """
+    Like :func:`replace` except it only replaces `text` with `repl` if `pattern` mathces the start
+    of `text`.
+
+    Args:
+        text: String to replace.
+        pattern: Pattern to find and replace.
+        repl: String to substitute `pattern` with.
+        ignore_case: Whether to ignore case when replacing. Defaults to ``False``.
+        escape: Whether to escape `pattern` when searching. This is needed if a
+            literal replacement is desired when `pattern` may contain special regular expression
+            characters. Defaults to ``True``.
+
+    Returns:
+        Replaced string.
+
+    Example:
+
+        >>> replace_start("aabbcc", "b", "X")
+        'aabbcc'
+        >>> replace_start("aabbcc", "a", "X")
+        'Xabbcc'
+
+    .. versionadded:: 4.1.0
+    """
+    return replace(text, pattern, repl, ignore_case=ignore_case, escape=escape, from_start=True)
+
+
+def separator_case(text: t.Any, separator: str) -> str:
+    """
+    Splits `text` on words and joins with `separator`.
+
+    Args:
+        text: String to convert.
+        separator: Separator to join words with.
+
+    Returns:
+        Converted string.
+
+    Example:
+
+        >>> separator_case("a!!b___c.d", "-")
+        'a-b-c-d'
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+    """
+    return separator.join(word.lower() for word in words(text) if word)
+
+
+def series_phrase(
+    items: t.List[t.Any],
+    separator: t.Any = ", ",
+    last_separator: t.Any = " and ",
+    serial: bool = False,
+) -> str:
+    """
+    Join items into a grammatical series phrase, e.g., ``"item1, item2, item3 and item4"``.
+
+    Args:
+        items: List of string items to join.
+        separator: Item separator. Defaults to ``', '``.
+        last_separator: Last item separator. Defaults to ``' and '``.
+        serial: Whether to include `separator` with `last_separator` when number of
+            items is greater than 2. Defaults to ``False``.
+
+    Returns:
+        Joined string.
+
+    Example:
+
+        >>> series_phrase(["apples", "bananas", "peaches"])
+        'apples, bananas and peaches'
+        >>> series_phrase(["apples", "bananas", "peaches"], serial=True)
+        'apples, bananas, and peaches'
+        >>> series_phrase(["apples", "bananas", "peaches"], "; ", ", or ")
+        'apples; bananas, or peaches'
+
+
+    .. versionadded:: 3.0.0
+    """
+    items = pyd.chain(items).map(pyd.to_string).compact().value()
+    item_count = len(items)
+
+    separator = pyd.to_string(separator)
+    last_separator = pyd.to_string(last_separator)
+
+    if item_count > 2 and serial:
+        last_separator = separator.rstrip() + last_separator
+
+    if item_count >= 2:
+        items = items[:-2] + [last_separator.join(items[-2:])]
+
+    return separator.join(items)
+
+
+def series_phrase_serial(
+    items: t.List[t.Any], separator: t.Any = ", ", last_separator: t.Any = " and "
+) -> str:
+    """
+    Join items into a grammatical series phrase using a serial separator, e.g., ``"item1, item2,
+    item3, and item4"``.
+
+    Args:
+        items: List of string items to join.
+        separator: Item separator. Defaults to ``', '``.
+        last_separator: Last item separator. Defaults to ``' and '``.
+
+    Returns:
+        Joined string.
+
+    Example:
+
+        >>> series_phrase_serial(["apples", "bananas", "peaches"])
+        'apples, bananas, and peaches'
+
+    .. versionadded:: 3.0.0
+    """
+    return series_phrase(items, separator, last_separator, serial=True)
+
+
+def slugify(text: t.Any, separator: str = "-") -> str:
+    """
+    Convert `text` into an ASCII slug which can be used safely in URLs. Incoming `text` is converted
+    to unicode and noramlzied using the ``NFKD`` form. This results in some accented characters
+    being converted to their ASCII "equivalent" (e.g. ``é`` is converted to ``e``). Leading and
+    trailing whitespace is trimmed and any remaining whitespace or other special characters without
+    an ASCII equivalent are replaced with ``-``.
+
+    Args:
+        text: String to slugify.
+        separator: Separator to use. Defaults to ``'-'``.
+
+    Returns:
+        Slugified string.
+
+    Example:
+
+        >>> slugify("This is a slug.") == "this-is-a-slug"
+        True
+        >>> slugify("This is a slug.", "+") == "this+is+a+slug"
+        True
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+
+    .. versionchanged:: 7.0.0
+        Remove single quotes from output.
+    """
+    normalized = (
+        unicodedata.normalize("NFKD", pyd.to_string(text))
+        .encode("ascii", "ignore")
+        .decode("utf8")
+        .replace("'", "")
+    )
+
+    return separator_case(normalized, separator)
+
+
+def snake_case(text: t.Any) -> str:
+    """
+    Converts `text` to snake case.
+
+    Args:
+        text: String to convert.
+
+    Returns:
+        String converted to snake case.
+
+    Example:
+
+        >>> snake_case("This is Snake Case!")
+        'this_is_snake_case'
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``underscore_case``.
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+    """
+    return "_".join(word.lower() for word in compounder(text) if word)
+
+
+def split(text: t.Any, separator: t.Union[str, Unset, None] = UNSET) -> t.List[str]:
+    """
+    Splits `text` on `separator`. If `separator` not provided, then `text` is split on whitespace.
+    If `separator` is falsey, then `text` is split on every character.
+
+    Args:
+        text: String to explode.
+        separator: Separator string to split on. Defaults to ``NoValue``.
+
+    Returns:
+        Split string.
+
+    Example:
+
+        >>> split("one potato, two potatoes, three potatoes, four!")
+        ['one', 'potato,', 'two', 'potatoes,', 'three', 'potatoes,', 'four!']
+        >>> split("one potato, two potatoes, three potatoes, four!", ",")
+        ['one potato', ' two potatoes', ' three potatoes', ' four!']
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 3.0.0
+        Changed `separator` default to ``NoValue`` and supported splitting on whitespace by default.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``explode``.
+    """
+    text = pyd.to_string(text)
+
+    if separator is UNSET:
+        ret = text.split()
+    elif separator:
+        ret = text.split(separator)
+    else:
+        ret = chars(text)
+
+    return ret
+
+
+def start_case(text: t.Any) -> str:
+    """
+    Convert `text` to start case.
+
+    Args:
+        text: String to convert.
+
+    Returns:
+        String converted to start case.
+
+    Example:
+
+        >>> start_case("fooBar")
+        'Foo Bar'
+
+    .. versionadded:: 3.1.0
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+    """
+    return " ".join(capitalize(word, strict=False) for word in compounder(text))
+
+
+def starts_with(text: t.Any, target: t.Any, position: int = 0) -> bool:
+    """
+    Checks if `text` starts with a given target string.
+
+    Args:
+        text: String to check.
+        target: String to check for.
+        position: Position to search from. Defaults to beginning of `text`.
+
+    Returns:
+        Whether `text` starts with `target`.
+
+    Example:
+
+        >>> starts_with("abcdef", "a")
+        True
+        >>> starts_with("abcdef", "b")
+        False
+        >>> starts_with("abcdef", "a", 1)
+        False
+
+    .. versionadded:: 1.1.0
+    """
+    text = pyd.to_string(text)
+    target = pyd.to_string(target)
+    return text[position:].startswith(target)
+
+
+def strip_tags(text: t.Any) -> str:
+    """
+    Removes all HTML tags from `text`.
+
+    Args:
+        text: String to strip.
+
+    Returns:
+        String without HTML tags.
+
+    Example:
+
+        >>> strip_tags('<a href="#">Some link</a>')
+        'Some link'
+
+    .. versionadded:: 3.0.0
+    """
+    return RE_HTML_TAGS.sub("", pyd.to_string(text))
+
+
+def substr_left(text: t.Any, subtext: str) -> str:
+    """
+    Searches `text` from left-to-right for `subtext` and returns a substring consisting of the
+    characters in `text` that are to the left of `subtext` or all string if no match found.
+
+    Args:
+        text: String to partition.
+        subtext: String to search for.
+
+    Returns:
+        Substring to left of `subtext`.
+
+    Example:
+
+        >>> substr_left("abcdefcdg", "cd")
+        'ab'
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    return text.partition(subtext)[0] if subtext else text
+
+
+def substr_left_end(text: t.Any, subtext: str) -> str:
+    """
+    Searches `text` from right-to-left for `subtext` and returns a substring consisting of the
+    characters in `text` that are to the left of `subtext` or all string if no match found.
+
+    Args:
+        text: String to partition.
+        subtext: String to search for.
+
+    Returns:
+        Substring to left of `subtext`.
+
+    Example:
+
+        >>> substr_left_end("abcdefcdg", "cd")
+        'abcdef'
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    return text.rpartition(subtext)[0] or text if subtext else text
+
+
+def substr_right(text: t.Any, subtext: str) -> str:
+    """
+    Searches `text` from right-to-left for `subtext` and returns a substring consisting of the
+    characters in `text` that are to the right of `subtext` or all string if no match found.
+
+    Args:
+        text: String to partition.
+        subtext: String to search for.
+
+    Returns:
+        Substring to right of `subtext`.
+
+    Example:
+
+        >>> substr_right("abcdefcdg", "cd")
+        'efcdg'
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    return text.partition(subtext)[2] or text if subtext else text
+
+
+def substr_right_end(text: t.Any, subtext: str) -> str:
+    """
+    Searches `text` from left-to-right for `subtext` and returns a substring consisting of the
+    characters in `text` that are to the right of `subtext` or all string if no match found.
+
+    Args:
+        text: String to partition.
+        subtext: String to search for.
+
+    Returns:
+        Substring to right of `subtext`.
+
+    Example:
+
+        >>> substr_right_end("abcdefcdg", "cd")
+        'g'
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    return text.rpartition(subtext)[2] if subtext else text
+
+
+def successor(char: t.Any) -> str:
+    """
+    Return the successor character of `char`.
+
+    Args:
+        char: Character to find the successor of.
+
+    Returns:
+        Successor character.
+
+    Example:
+
+        >>> successor("b")
+        'c'
+        >>> successor("B")
+        'C'
+        >>> successor("2")
+        '3'
+
+    .. versionadded:: 3.0.0
+    """
+    char = pyd.to_string(char)
+    return chr(ord(char) + 1)
+
+
+def surround(text: t.Any, wrapper: t.Any) -> str:
+    """
+    Surround a string with another string.
+
+    Args:
+        text: String to surround with `wrapper`.
+        wrapper: String by which `text` is to be surrounded.
+
+    Returns:
+        Surrounded string.
+
+    Example:
+
+        >>> surround("abc", '"')
+        '"abc"'
+        >>> surround("abc", "!")
+        '!abc!'
+
+    .. versionadded:: 2.4.0
+    """
+    text = pyd.to_string(text)
+    wrapper = pyd.to_string(wrapper)
+    return f"{wrapper}{text}{wrapper}"
+
+
+def swap_case(text: t.Any) -> str:
+    """
+    Swap case of `text` characters.
+
+    Args:
+        text: String to swap case.
+
+    Returns:
+        String with swapped case.
+
+    Example:
+
+        >>> swap_case("aBcDeF")
+        'AbCdEf'
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    return text.swapcase()
+
+
+def title_case(text: t.Any) -> str:
+    """
+    Convert `text` to title case.
+
+    Args:
+        text: String to convert.
+
+    Returns:
+        String converted to title case.
+
+    Example:
+
+        >>> title_case("bob's shop")
+        "Bob's Shop"
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    # NOTE: Can't use text.title() since it doesn't handle apostrophes.
+    return " ".join(word.capitalize() for word in re.split(" ", text))
+
+
+def to_lower(text: t.Any) -> str:
+    """
+    Converts the given :attr:`text` to lower text.
+
+    Args:
+        text: String to convert.
+
+    Returns:
+        String converted to lower case.
+
+    Example:
+
+        >>> to_lower("--Foo-Bar--")
+        '--foo-bar--'
+        >>> to_lower("fooBar")
+        'foobar'
+        >>> to_lower("__FOO_BAR__")
+        '__foo_bar__'
+
+    .. versionadded:: 4.0.0
+    """
+    return pyd.to_string(text).lower()
+
+
+def to_upper(text: t.Any) -> str:
+    """
+    Converts the given :attr:`text` to upper text.
+
+    Args:
+        text: String to convert.
+
+    Returns:
+        String converted to upper case.
+
+    Example:
+
+        >>> to_upper("--Foo-Bar--")
+        '--FOO-BAR--'
+        >>> to_upper("fooBar")
+        'FOOBAR'
+        >>> to_upper("__FOO_BAR__")
+        '__FOO_BAR__'
+
+    .. versionadded:: 4.0.0
+    """
+    return pyd.to_string(text).upper()
+
+
+def trim(text: t.Any, chars: t.Union[str, None] = None) -> str:
+    r"""
+    Removes leading and trailing whitespace or specified characters from `text`.
+
+    Args:
+        text: String to trim.
+        chars: Specific characters to remove.
+
+    Returns:
+        Trimmed string.
+
+    Example:
+
+        >>> trim("  abc efg\r\n ")
+        'abc efg'
+
+    .. versionadded:: 1.1.0
+    """
+    # pylint: disable=redefined-outer-name
+    text = pyd.to_string(text)
+    return text.strip(chars)
+
+
+def trim_end(text: t.Any, chars: t.Union[str, None] = None) -> str:
+    r"""
+    Removes trailing whitespace or specified characters from `text`.
+
+    Args:
+        text: String to trim.
+        chars: Specific characters to remove.
+
+    Returns:
+        Trimmed string.
+
+    Example:
+
+        >>> trim_end("  abc efg\r\n ")
+        '  abc efg'
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``trim_right`` to ``trim_end``.
+    """
+    text = pyd.to_string(text)
+    return text.rstrip(chars)
+
+
+def trim_start(text: t.Any, chars: t.Union[str, None] = None) -> str:
+    r"""
+    Removes leading  whitespace or specified characters from `text`.
+
+    Args:
+        text: String to trim.
+        chars: Specific characters to remove.
+
+    Returns:
+        Trimmed string.
+
+    Example:
+
+        >>> trim_start("  abc efg\r\n ")
+        'abc efg\r\n '
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 4.0.0
+        Renamed from ``trim_left`` to ``trim_start``.
+    """
+    text = pyd.to_string(text)
+    return text.lstrip(chars)
+
+
+def truncate(
+    text: t.Any,
+    length: int = 30,
+    omission: str = "...",
+    separator: t.Union[str, re.Pattern[str], None] = None,
+) -> str:
+    """
+    Truncates `text` if it is longer than the given maximum string length. The last characters of
+    the truncated string are replaced with the omission string which defaults to ``...``.
+
+    Args:
+        text: String to truncate.
+        length: Maximum string length. Defaults to ``30``.
+        omission: String to indicate text is omitted.
+        separator: Separator pattern to truncate to.
+
+    Returns:
+        Truncated string.
+
+    Example:
+
+        >>> truncate("hello world", 5)
+        'he...'
+        >>> truncate("hello world", 5, "..")
+        'hel..'
+        >>> truncate("hello world", 10)
+        'hello w...'
+        >>> truncate("hello world", 10, separator=" ")
+        'hello...'
+
+    .. versionadded:: 1.1.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``trunc``.
+    """
+    text = pyd.to_string(text)
+
+    if len(text) <= length:
+        return text
+
+    omission_len = len(omission)
+    text_len = length - omission_len
+    text = text[:text_len]
+
+    trunc_len = len(text)
+
+    if pyd.is_string(separator):
+        trunc_len = text.rfind(separator)
+    elif pyd.is_reg_exp(separator):
+        last = None
+        for match in separator.finditer(text):
+            last = match
+
+        if last is not None:
+            trunc_len = last.start()
+
+    return text[:trunc_len] + omission
+
+
+def unescape(text: t.Any) -> str:
+    """
+    The inverse of :func:`escape`. This method converts the HTML entities ``&amp;``, ``&lt;``,
+    ``&gt;``, ``&quot;``, ``&#39;``, and ``&#96;`` in `text` to their corresponding characters.
+
+    Args:
+        text: String to unescape.
+
+    Returns:
+        HTML unescaped string.
+
+    Example:
+
+        >>> results = unescape("&quot;1 &gt; 2 &amp;&amp; 3 &lt; 4&quot;")
+        >>> results == '"1 > 2 && 3 < 4"'
+        True
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 1.1.0
+        Moved to :mod:`pydash.strings`.
+    """
+    text = pyd.to_string(text)
+    return html.unescape(text)
+
+
+def upper_case(text: t.Any) -> str:
+    """
+    Converts string to upper case, as space separated words.
+
+    Args:
+        text: String to be converted to uppercase.
+
+    Returns:
+        String converted to uppercase, as space separated words.
+
+    Example:
+
+        >>> upper_case("--foo-bar--")
+        'FOO BAR'
+        >>> upper_case("fooBar")
+        'FOO BAR'
+        >>> upper_case('/?*Foo10/;"B*Ar')
+        'FOO 10 B AR'
+
+    .. versionadded:: 4.0.0
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+    """
+    return " ".join(compounder(text)).upper()
+
+
+def upper_first(text: str) -> str:
+    """
+    Converts the first character of string to upper case.
+
+    Args:
+        text: String passed in by the user.
+
+    Returns:
+        String in which the first character is converted to upper case.
+
+    Example:
+
+        >>> upper_first("fred")
+        'Fred'
+        >>> upper_first("foo bar")
+        'Foo bar'
+        >>> upper_first("1foobar")
+        '1foobar'
+        >>> upper_first(";foobar")
+        ';foobar'
+
+    .. versionadded:: 4.0.0
+    """
+    return text[:1].upper() + text[1:]
+
+
+def unquote(text: t.Any, quote_char: t.Any = '"') -> str:
+    """
+    Unquote `text` by removing `quote_char` if `text` begins and ends with it.
+
+    Args:
+        text: String to unquote.
+        quote_char: Quote character to remove. Defaults to `"`.
+
+    Returns:
+        Unquoted string.
+
+    Example:
+
+        >>> unquote('"abc"')
+        'abc'
+        >>> unquote('"abc"', "#")
+        '"abc"'
+        >>> unquote("#abc", "#")
+        '#abc'
+        >>> unquote("#abc#", "#")
+        'abc'
+
+    .. versionadded:: 3.0.0
+    """
+    text = pyd.to_string(text)
+    inner = text[1:-1]
+
+    if text == f"{quote_char}{inner}{quote_char}":
+        text = inner
+
+    return text
+
+
+def url(*paths: t.Any, **params: t.Any) -> str:
+    """
+    Combines a series of URL paths into a single URL. Optionally, pass in keyword arguments to
+    append query parameters.
+
+    Args:
+        paths: URL paths to combine.
+
+    Keyword Args:
+        params: Query parameters.
+
+    Returns:
+        URL string.
+
+    Example:
+
+        >>> link = url("a", "b", ["c", "d"], "/", q="X", y="Z")
+        >>> path, params = link.split("?")
+        >>> path == "a/b/c/d/"
+        True
+        >>> set(params.split("&")) == set(["q=X", "y=Z"])
+        True
+
+    .. versionadded:: 2.2.0
+    """
+    # allow reassignment different type
+    paths = pyd.chain(paths).flatten_deep().map(pyd.to_string).value()  # type: ignore
+    paths_list = []
+    params_list = flatten_url_params(params)
+
+    for path in paths:
+        scheme, netloc, path, query, fragment = urlsplit(path)
+        query = parse_qsl(query)
+        params_list += query
+        paths_list.append(urlunsplit((scheme, netloc, path, "", fragment)))
+
+    path = delimitedpathjoin("/", *paths_list)
+    scheme, netloc, path, query, fragment = urlsplit(path)
+    query = urlencode(params_list)
+
+    return urlunsplit((scheme, netloc, path, query, fragment))
+
+
+def words(text: t.Any, pattern: t.Union[str, None] = None) -> t.List[str]:
+    """
+    Return list of words contained in `text`.
+
+    References:
+        https://github.com/lodash/lodash/blob/master/words.js#L30
+
+    Args:
+        text: String to split.
+        pattern: Custom pattern to split words on. Defaults to ``None``.
+
+    Returns:
+        List of words.
+
+    Example:
+
+        >>> words("a b, c; d-e")
+        ['a', 'b', 'c', 'd', 'e']
+        >>> words("fred, barney, & pebbles", "/[^, ]+/g")
+        ['fred', 'barney', '&', 'pebbles']
+
+    .. versionadded:: 2.0.0
+
+    .. versionchanged:: 3.2.0
+        Added `pattern` argument.
+
+    .. versionchanged:: 3.2.0
+        Improved matching for one character words.
+
+    .. versionchanged:: 5.0.0
+        Improved unicode word support.
+    """
+    text = pyd.to_string(text)
+    if pattern is None:
+        if has_unicode_word(text):
+            reg_exp = JS_RE_UNICODE_WORDS
+        else:
+            reg_exp = JS_RE_ASCII_WORDS
+    else:
+        reg_exp = JSRegExp(pattern)
+    return reg_exp.find(text)
+
+
+#
+# Utility functions not a part of main API
+#
+
+
+def compounder(text):
+    """
+    Remove single quote before passing into words() to match Lodash-style outputs.
+
+    Required by certain functions such as kebab_case, camel_case, start_case etc.
+
+    References:
+        https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L4968
+    """
+    return words(deburr(RE_APOS.sub("", pyd.to_string(text))))
+
+
+def has_unicode_word(text):
+    """
+    Check if the text contains unicode or requires more complex regex to handle.
+
+    References:
+        https://github.com/lodash/lodash/blob/master/words.js#L3
+    """
+    result = RE_HAS_UNICODE_WORD.search(text)
+    return bool(result)
+
+
+def delimitedpathjoin(delimiter, *paths):
+    """
+    Join delimited path using specified delimiter.
+
+    >>> assert delimitedpathjoin(".", "") == ""
+    >>> assert delimitedpathjoin(".", ".") == "."
+    >>> assert delimitedpathjoin(".", ["", ".a"]) == ".a"
+    >>> assert delimitedpathjoin(".", ["a", "."]) == "a."
+    >>> assert delimitedpathjoin(".", ["", ".a", "", "", "b"]) == ".a.b"
+    >>> ret = ".a.b.c.d.e."
+    >>> assert delimitedpathjoin(".", [".a.", "b.", ".c", "d", "e."]) == ret
+    >>> assert delimitedpathjoin(".", ["a", "b", "c"]) == "a.b.c"
+    >>> ret = "a.b.c.d.e.f"
+    >>> assert delimitedpathjoin(".", ["a.b", ".c.d.", ".e.f"]) == ret
+    >>> ret = ".a.b.c.1."
+    >>> assert delimitedpathjoin(".", ".", "a", "b", "c", 1, ".") == ret
+    >>> assert delimitedpathjoin(".", []) == ""
+    """
+    paths = [pyd.to_string(path) for path in pyd.flatten_deep(paths) if path]
+
+    if len(paths) == 1:
+        # Special case where there's no need to join anything. Doing this because if
+        # path==[delimiter], then an extra delimiter would be added if the else clause ran instead.
+        path = paths[0]
+    else:
+        leading = delimiter if paths and paths[0].startswith(delimiter) else ""
+        trailing = delimiter if paths and paths[-1].endswith(delimiter) else ""
+        middle = delimiter.join([path.strip(delimiter) for path in paths if path.strip(delimiter)])
+        path = "".join([leading, middle, trailing])
+
+    return path
+
+
+def flatten_url_params(
+    params: t.Union[
+        t.Dict[T, t.Union[T2, t.Iterable[T2]]],
+        t.List[t.Tuple[T, t.Union[T2, t.Iterable[T2]]]],
+    ],
+) -> t.List[t.Tuple[T, T2]]:
+    """
+    Flatten URL params into list of tuples. If any param value is a list or tuple, then map each
+    value to the param key.
+
+    >>> params = [("a", 1), ("a", [2, 3])]
+    >>> assert flatten_url_params(params) == [("a", 1), ("a", 2), ("a", 3)]
+    >>> params = {"a": [1, 2, 3]}
+    >>> assert flatten_url_params(params) == [("a", 1), ("a", 2), ("a", 3)]
+    """
+    if isinstance(params, dict):
+        params = list(params.items())
+
+    flattened: t.List[t.Any] = []
+    for param, value in params:
+        if isinstance(value, (list, tuple)):
+            flattened += zip([param] * len(value), value)
+        else:
+            flattened.append((param, value))
+
+    return flattened
diff --git a/.venv/lib/python3.12/site-packages/pydash/types.py b/.venv/lib/python3.12/site-packages/pydash/types.py
new file mode 100644
index 00000000..a909677c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/types.py
@@ -0,0 +1,30 @@
+"""
+Common types.
+
+.. versionadded:: 7.0.0
+"""
+
+from __future__ import annotations
+
+from decimal import Decimal
+import typing as t
+
+from typing_extensions import Protocol
+
+
+IterateeObjT = t.Union[int, str, t.List[t.Any], t.Tuple[t.Any, ...], t.Dict[t.Any, t.Any]]
+NumberT = t.Union[float, int, Decimal]
+NumberNoDecimalT = t.Union[float, int]
+PathT = t.Union[t.Hashable, t.List[t.Hashable]]
+
+
+_T_co = t.TypeVar("_T_co", covariant=True)
+_T_contra = t.TypeVar("_T_contra", contravariant=True)
+
+
+class SupportsMul(Protocol[_T_contra, _T_co]):
+    def __mul__(self, x: _T_contra) -> _T_co: ...
+
+
+class SupportsRound(Protocol[_T_co]):
+    def __round__(self) -> _T_co: ...
diff --git a/.venv/lib/python3.12/site-packages/pydash/utilities.py b/.venv/lib/python3.12/site-packages/pydash/utilities.py
new file mode 100644
index 00000000..e55e5d28
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pydash/utilities.py
@@ -0,0 +1,1511 @@
+"""
+Utility functions.
+
+.. versionadded:: 1.0.0
+"""
+
+from __future__ import annotations
+
+from collections import namedtuple
+from datetime import datetime, timezone
+from functools import partial, wraps
+import math
+from random import randint, uniform
+import re
+import time
+import typing as t
+
+from typing_extensions import Literal, ParamSpec, Protocol, Type
+
+import pydash as pyd
+
+from .helpers import NUMBER_TYPES, UNSET, base_get, callit, getargcount, iterator
+from .types import PathT
+
+
+__all__ = (
+    "attempt",
+    "cond",
+    "conforms",
+    "conforms_to",
+    "constant",
+    "default_to",
+    "default_to_any",
+    "identity",
+    "iteratee",
+    "matches",
+    "matches_property",
+    "memoize",
+    "method",
+    "method_of",
+    "noop",
+    "nth_arg",
+    "now",
+    "over",
+    "over_every",
+    "over_some",
+    "properties",
+    "property_",
+    "property_of",
+    "random",
+    "range_",
+    "range_right",
+    "result",
+    "retry",
+    "stub_list",
+    "stub_dict",
+    "stub_false",
+    "stub_string",
+    "stub_true",
+    "times",
+    "to_path",
+    "unique_id",
+)
+
+T = t.TypeVar("T")
+T2 = t.TypeVar("T2")
+CallableT = t.TypeVar("CallableT", bound=t.Callable[..., t.Any])
+P = ParamSpec("P")
+
+# These regexes are used in to_path() to parse deep path strings.
+
+# This is used to split a deep path string into dict keys or list indexes. This matches "." as
+# delimiter (unless it is escaped by "//") and "[<integer>]" as delimiter while keeping the
+# "[<integer>]" as an item.
+RE_PATH_KEY_DELIM = re.compile(r"(?<!\\)(?:\\\\)*\.|(\[-?\d+\])")
+
+# Matches on path strings like "[<integer>]". This is used to test whether a path string part is a
+# list index.
+RE_PATH_LIST_INDEX = re.compile(r"^\[-?\d+\]$")
+
+
+ID_COUNTER = 0
+
+PathToken = namedtuple("PathToken", ["key", "default_factory"])
+
+
+def attempt(func: t.Callable[P, T], *args: "P.args", **kwargs: "P.kwargs") -> t.Union[T, Exception]:
+    """
+    Attempts to execute `func`, returning either the result or the caught error object.
+
+    Args:
+        func: The function to attempt.
+
+    Returns:
+        Returns the `func` result or error object.
+
+    Example:
+
+        >>> results = attempt(lambda x: x / 0, 1)
+        >>> assert isinstance(results, ZeroDivisionError)
+
+    .. versionadded:: 1.1.0
+    """
+    try:
+        ret = func(*args, **kwargs)
+    except Exception as ex:
+        # allow different type reassignment
+        ret = ex  # type: ignore
+
+    return ret
+
+
+@t.overload
+def cond(
+    pairs: t.List[t.Tuple[t.Callable[P, t.Any], t.Callable[P, T]]],
+    *extra_pairs: t.Tuple[t.Callable[P, t.Any], t.Callable[P, T]],
+) -> t.Callable[P, T]: ...
+
+
+@t.overload
+def cond(
+    pairs: t.List[t.List[t.Callable[P, t.Any]]], *extra_pairs: t.List[t.Callable[P, t.Any]]
+) -> t.Callable[P, t.Any]: ...
+
+
+def cond(pairs, *extra_pairs):
+    """
+    Creates a function that iterates over `pairs` and invokes the corresponding function of the
+    first predicate to return truthy.
+
+    Args:
+        pairs: A list of predicate-function pairs.
+
+    Returns:
+        Returns the new composite function.
+
+    Example:
+
+        >>> func = cond([[matches({'a': 1}), constant('matches A')],\
+                         [matches({'b': 2}), constant('matches B')],\
+                         [stub_true, lambda value: value]])
+        >>> func({'a': 1, 'b': 2})
+        'matches A'
+        >>> func({'a': 0, 'b': 2})
+        'matches B'
+        >>> func({'a': 0, 'b': 0}) == {'a': 0, 'b': 0}
+        True
+
+    .. versionadded:: 4.0.0
+
+    .. versionchanged:: 4.2.0
+        Fixed missing argument passing to matched function and added support for passing in a single
+        list of pairs instead of just pairs as separate arguments.
+    """
+    if extra_pairs:
+        pairs = [pairs] + list(extra_pairs)
+
+    for pair in pairs:
+        is_valid = False
+        try:
+            is_valid = len(pair) == 2
+        except Exception:
+            pass
+
+        if not is_valid:
+            raise ValueError("Each predicate-function pair should contain exactly two elements")
+
+        if not all(map(callable, pair)):
+            raise TypeError("Both predicate-function pair should be callable")
+
+    def _cond(*args):
+        for pair in pairs:
+            predicate, iteratee = pair
+
+            if callit(predicate, *args):
+                return iteratee(*args)
+
+    return _cond
+
+
+@t.overload
+def conforms(source: t.Dict[T, t.Callable[[T2], t.Any]]) -> t.Callable[[t.Dict[T, T2]], bool]: ...
+
+
+@t.overload
+def conforms(source: t.List[t.Callable[[T], t.Any]]) -> t.Callable[[t.List[T]], bool]: ...
+
+
+def conforms(source: t.Union[t.List[t.Any], t.Dict[t.Any, t.Any]]) -> t.Callable[..., t.Any]:
+    """
+    Creates a function that invokes the predicate properties of `source` with the corresponding
+    property values of a given object, returning ``True`` if all predicates return truthy, else
+    ``False``.
+
+    Args:
+        source: The object of property predicates to conform to.
+
+    Returns:
+        Returns the new spec function.
+
+    Example:
+
+        >>> func = conforms({"b": lambda n: n > 1})
+        >>> func({"b": 2})
+        True
+        >>> func({"b": 0})
+        False
+        >>> func = conforms([lambda n: n > 1, lambda n: n == 0])
+        >>> func([2, 0])
+        True
+        >>> func([0, 0])
+        False
+
+    .. versionadded:: 4.0.0
+    """
+
+    def _conforms(obj):
+        for key, predicate in iterator(source):
+            if not pyd.has(obj, key) or not predicate(obj[key]):
+                return False
+        return True
+
+    return _conforms
+
+
+@t.overload
+def conforms_to(obj: t.Dict[T, T2], source: t.Dict[T, t.Callable[[T2], t.Any]]) -> bool: ...
+
+
+@t.overload
+def conforms_to(obj: t.List[T], source: t.List[t.Callable[[T], t.Any]]) -> bool: ...
+
+
+def conforms_to(obj, source):
+    """
+    Checks if `obj` conforms to `source` by invoking the predicate properties of `source` with the
+    corresponding property values of `obj`.
+
+    Args:
+        obj: The object to inspect.
+        source: The object of property predicates to conform to.
+
+    Example:
+
+        >>> conforms_to({"b": 2}, {"b": lambda n: n > 1})
+        True
+        >>> conforms_to({"b": 0}, {"b": lambda n: n > 1})
+        False
+        >>> conforms_to([2, 0], [lambda n: n > 1, lambda n: n == 0])
+        True
+        >>> conforms_to([0, 0], [lambda n: n > 1, lambda n: n == 0])
+        False
+
+    .. versionadded:: 4.0.0
+    """
+    return conforms(source)(obj)
+
+
+def constant(value: T) -> t.Callable[..., T]:
+    """
+    Creates a function that returns `value`.
+
+    Args:
+        value: Constant value to return.
+
+    Returns:
+        Function that always returns `value`.
+
+    Example:
+
+        >>> pi = constant(3.14)
+        >>> pi() == 3.14
+        True
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.0
+        Returned function ignores arguments instead of raising exception.
+    """
+    return partial(identity, value)
+
+
+def default_to(value: t.Union[T, None], default_value: T2) -> t.Union[T, T2]:
+    """
+    Checks `value` to determine whether a default value should be returned in its place. The
+    `default_value` is returned if value is None.
+
+    Args:
+        default_value: Default value passed in by the user.
+
+    Returns:
+        Returns `value` if :attr:`value` is given otherwise returns `default_value`.
+
+    Example:
+
+        >>> default_to(1, 10)
+        1
+        >>> default_to(None, 10)
+        10
+
+    .. versionadded:: 4.0.0
+    """
+    return default_to_any(value, default_value)
+
+
+@t.overload
+def default_to_any(value: None, *default_values: None) -> None: ...
+
+
+@t.overload
+def default_to_any(
+    value: t.Union[T, None],
+    default_value1: None,
+    default_value2: T2,
+) -> t.Union[T, T2]: ...
+
+
+@t.overload
+def default_to_any(
+    value: t.Union[T, None],
+    default_value1: None,
+    default_value2: None,
+    default_value3: T2,
+) -> t.Union[T, T2]: ...
+
+
+@t.overload
+def default_to_any(
+    value: t.Union[T, None],
+    default_value1: None,
+    default_value2: None,
+    default_value3: None,
+    default_value4: T2,
+) -> t.Union[T, T2]: ...
+
+
+@t.overload
+def default_to_any(
+    value: t.Union[T, None],
+    default_value1: None,
+    default_value2: None,
+    default_value3: None,
+    default_value4: None,
+    default_value5: T2,
+) -> t.Union[T, T2]: ...
+
+
+@t.overload
+def default_to_any(value: t.Union[T, None], *default_values: T2) -> t.Union[T, T2]: ...
+
+
+def default_to_any(value, *default_values):
+    """
+    Checks `value` to determine whether a default value should be returned in its place. The first
+    item that is not None of the `default_values` is returned.
+
+    Args:
+        value: Value passed in by the user.
+        *default_values: Default values passed in by the user.
+
+    Returns:
+        Returns `value` if :attr:`value` is given otherwise returns the first not None value
+            of `default_values`.
+
+    Example:
+
+        >>> default_to_any(1, 10, 20)
+        1
+        >>> default_to_any(None, 10, 20)
+        10
+        >>> default_to_any(None, None, 20)
+        20
+
+
+    .. versionadded:: 4.9.0
+    """
+    values = (value,) + default_values
+    for val in values:
+        if val is not None:
+            return val
+
+
+@t.overload
+def identity(arg: T, *args: t.Any) -> T: ...
+
+
+@t.overload
+def identity(arg: None = None, *args: t.Any) -> None: ...
+
+
+def identity(arg=None, *args):
+    """
+    Return the first argument provided to it.
+
+    Args:
+        *args: Arguments.
+
+    Returns:
+        First argument or ``None``.
+
+    Example:
+
+        >>> identity(1)
+        1
+        >>> identity(1, 2, 3)
+        1
+        >>> identity() is None
+        True
+
+    .. versionadded:: 1.0.0
+    """
+    return arg
+
+
+@t.overload
+def iteratee(func: t.Callable[P, T]) -> t.Callable[P, T]: ...
+
+
+@t.overload
+def iteratee(func: t.Any) -> t.Callable[..., t.Any]: ...
+
+
+def iteratee(func):
+    """
+    Return a pydash style iteratee. If `func` is a property name the created iteratee will return
+    the property value for a given element. If `func` is an object the created iteratee will return
+    ``True`` for elements that contain the equivalent object properties, otherwise it will return
+    ``False``.
+
+    Args:
+        func: Object to create iteratee function from.
+
+    Returns:
+        Iteratee function.
+
+    Example:
+
+        >>> get_data = iteratee("data")
+        >>> get_data({"data": [1, 2, 3]})
+        [1, 2, 3]
+        >>> is_active = iteratee({"active": True})
+        >>> is_active({"active": True})
+        True
+        >>> is_active({"active": 0})
+        False
+        >>> iteratee(["a", 5])({"a": 5})
+        True
+        >>> iteratee(["a.b"])({"a.b": 5})
+        5
+        >>> iteratee("a.b")({"a": {"b": 5}})
+        5
+        >>> iteratee(("a", ["c", "d", "e"]))({"a": 1, "c": {"d": {"e": 3}}})
+        [1, 3]
+        >>> iteratee(lambda a, b: a + b)(1, 2)
+        3
+        >>> ident = iteratee(None)
+        >>> ident("a")
+        'a'
+        >>> ident(1, 2, 3)
+        1
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 2.0.0
+        Renamed ``create_iteratee()`` to :func:`iteratee`.
+
+    .. versionchanged:: 3.0.0
+        Made pluck style iteratee support deep property access.
+
+    .. versionchanged:: 3.1.0
+        - Added support for shallow pluck style property access via single item list/tuple.
+        - Added support for matches property style iteratee via two item list/tuple.
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``callback``.
+
+    .. versionchanged:: 4.1.0
+        Return :func:`properties` callback when `func` is a ``tuple``.
+    """
+    if callable(func):
+        cbk = func
+    else:
+        if isinstance(func, int):
+            func = str(func)
+
+        if isinstance(func, str):
+            cbk = property_(func)
+        elif isinstance(func, list) and len(func) == 1:
+            cbk = property_(func)
+        elif isinstance(func, list) and len(func) > 1:
+            cbk = matches_property(*func[:2])
+        elif isinstance(func, tuple):
+            cbk = properties(*func)
+        elif isinstance(func, dict):
+            cbk = matches(func)
+        else:
+            cbk = identity
+
+        # Optimize iteratee by specifying the exact number of arguments the iteratee takes so that
+        # arg inspection (costly process) can be skipped in helpers.callit().
+        cbk._argcount = 1
+
+    return cbk
+
+
+def matches(source: t.Any) -> t.Callable[[t.Any], bool]:
+    """
+    Creates a matches-style predicate function which performs a deep comparison between a given
+    object and the `source` object, returning ``True`` if the given object has equivalent property
+    values, else ``False``.
+
+    Args:
+        source: Source object used for comparision.
+
+    Returns:
+        Function that compares an object to `source` and returns whether the two objects
+            contain the same items.
+
+    Example:
+
+        >>> matches({"a": {"b": 2}})({"a": {"b": 2, "c": 3}})
+        True
+        >>> matches({"a": 1})({"b": 2, "a": 1})
+        True
+        >>> matches({"a": 1})({"b": 2, "a": 2})
+        False
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 3.0.0
+        Use :func:`pydash.predicates.is_match` as matching function.
+    """
+    return lambda obj: pyd.is_match(obj, source)
+
+
+def matches_property(key: t.Any, value: t.Any) -> t.Callable[[t.Any], bool]:
+    """
+    Creates a function that compares the property value of `key` on a given object to `value`.
+
+    Args:
+        key: Object key to match against.
+        value: Value to compare to.
+
+    Returns:
+        Function that compares `value` to an object's `key` and returns whether they are
+            equal.
+
+    Example:
+
+        >>> matches_property("a", 1)({"a": 1, "b": 2})
+        True
+        >>> matches_property(0, 1)([1, 2, 3])
+        True
+        >>> matches_property("a", 2)({"a": 1, "b": 2})
+        False
+
+    .. versionadded:: 3.1.0
+    """
+    prop_accessor = property_(key)
+    return lambda obj: matches(value)(prop_accessor(obj))
+
+
+class MemoizedFunc(Protocol[P, T, T2]):
+    cache: t.Dict[T2, T]
+
+    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: ...  # pragma: no cover
+
+
+@t.overload
+def memoize(func: t.Callable[P, T], resolver: None = None) -> MemoizedFunc[P, T, str]: ...
+
+
+@t.overload
+def memoize(
+    func: t.Callable[P, T], resolver: t.Union[t.Callable[P, T2], None] = None
+) -> MemoizedFunc[P, T, T2]: ...
+
+
+def memoize(func, resolver=None):
+    """
+    Creates a function that memoizes the result of `func`. If `resolver` is provided it will be used
+    to determine the cache key for storing the result based on the arguments provided to the
+    memoized function. By default, all arguments provided to the memoized function are used as the
+    cache key. The result cache is exposed as the cache property on the memoized function.
+
+    Args:
+        func: Function to memoize.
+        resolver: Function that returns the cache key to use.
+
+    Returns:
+        Memoized function.
+
+    Example:
+
+        >>> ident = memoize(identity)
+        >>> ident(1)
+        1
+        >>> ident.cache["(1,){}"] == 1
+        True
+        >>> ident(1, 2, 3)
+        1
+        >>> ident.cache["(1, 2, 3){}"] == 1
+        True
+
+    .. versionadded:: 1.0.0
+    """
+
+    def memoized(*args: P.args, **kwargs: P.kwargs):
+        if resolver:
+            key = resolver(*args, **kwargs)
+        else:
+            key = f"{args}{kwargs}"
+
+        if key not in memoized.cache:  # type: ignore
+            memoized.cache[key] = func(*args, **kwargs)  # type:ignore
+
+        return memoized.cache[key]  # type: ignore
+
+    memoized.cache = {}
+
+    return memoized
+
+
+def method(path: PathT, *args: t.Any, **kwargs: t.Any) -> t.Callable[..., t.Any]:
+    """
+    Creates a function that invokes the method at `path` on a given object. Any additional arguments
+    are provided to the invoked method.
+
+    Args:
+        path: Object path of method to invoke.
+        *args: Global arguments to apply to method when invoked.
+        **kwargs: Global keyword argument to apply to method when invoked.
+
+    Returns:
+        Function that invokes method located at path for object.
+
+    Example:
+
+        >>> obj = {"a": {"b": [None, lambda x: x]}}
+        >>> echo = method("a.b.1")
+        >>> echo(obj, 1) == 1
+        True
+        >>> echo(obj, "one") == "one"
+        True
+
+    .. versionadded:: 3.3.0
+    """
+
+    def _method(obj, *_args, **_kwargs):
+        func = pyd.partial(pyd.get(obj, path), *args, **kwargs)
+        return func(*_args, **_kwargs)
+
+    return _method
+
+
+def method_of(obj: t.Any, *args: t.Any, **kwargs: t.Any) -> t.Callable[..., t.Any]:
+    """
+    The opposite of :func:`method`. This method creates a function that invokes the method at a
+    given path on object. Any additional arguments are provided to the invoked method.
+
+    Args:
+        obj: The object to query.
+        *args: Global arguments to apply to method when invoked.
+        **kwargs: Global keyword argument to apply to method when invoked.
+
+    Returns:
+        Function that invokes method located at path for object.
+
+    Example:
+
+        >>> obj = {"a": {"b": [None, lambda x: x]}}
+        >>> dispatch = method_of(obj)
+        >>> dispatch("a.b.1", 1) == 1
+        True
+        >>> dispatch("a.b.1", "one") == "one"
+        True
+
+    .. versionadded:: 3.3.0
+    """
+
+    def _method_of(path, *_args, **_kwargs):
+        func = pyd.partial(pyd.get(obj, path), *args, **kwargs)
+        return func(*_args, **_kwargs)
+
+    return _method_of
+
+
+def noop(*args: t.Any, **kwargs: t.Any) -> None:  # pylint: disable=unused-argument
+    """
+    A no-operation function.
+
+    .. versionadded:: 1.0.0
+    """
+    pass
+
+
+def nth_arg(pos: int = 0) -> t.Callable[..., t.Any]:
+    """
+    Creates a function that gets the argument at index n. If n is negative, the nth argument from
+    the end is returned.
+
+    Args:
+        pos: The index of the argument to return.
+
+    Returns:
+        Returns the new pass-thru function.
+
+    Example:
+
+        >>> func = nth_arg(1)
+        >>> func(11, 22, 33, 44)
+        22
+        >>> func = nth_arg(-1)
+        >>> func(11, 22, 33, 44)
+        44
+
+    .. versionadded:: 4.0.0
+    """
+
+    def _nth_arg(*args):
+        try:
+            position = math.ceil(float(pos))
+        except ValueError:
+            position = 0
+
+        return pyd.get(args, position)
+
+    return _nth_arg
+
+
+def now() -> int:
+    """
+    Return the number of milliseconds that have elapsed since the Unix epoch (1 January 1970
+    00:00:00 UTC).
+
+    Returns:
+        Milliseconds since Unix epoch.
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 3.0.0
+        Use ``datetime`` module for calculating elapsed time.
+    """
+    epoch = datetime.fromtimestamp(0, timezone.utc)
+    delta = datetime.now(timezone.utc) - epoch
+    return int(delta.total_seconds() * 1000)
+
+
+def over(funcs: t.Iterable[t.Callable[P, T]]) -> t.Callable[P, t.List[T]]:
+    """
+    Creates a function that invokes all functions in `funcs` with the arguments it receives and
+    returns their results.
+
+    Args:
+        funcs: List of functions to be invoked.
+
+    Returns:
+        Returns the new pass-thru function.
+
+    Example:
+
+        >>> func = over([max, min])
+        >>> func(1, 2, 3, 4)
+        [4, 1]
+
+    .. versionadded:: 4.0.0
+    """
+
+    def _over(*args: P.args, **kwargs: P.kwargs) -> t.List[T]:
+        return [func(*args, **kwargs) for func in funcs]
+
+    return _over
+
+
+def over_every(funcs: t.Iterable[t.Callable[P, t.Any]]) -> t.Callable[P, bool]:
+    """
+    Creates a function that checks if all the functions in `funcs` return truthy when invoked with
+    the arguments it receives.
+
+    Args:
+        funcs: List of functions to be invoked.
+
+    Returns:
+        Returns the new pass-thru function.
+
+    Example:
+
+        >>> func = over_every([bool, lambda x: x is not None])
+        >>> func(1)
+        True
+
+    .. versionadded:: 4.0.0
+    """
+
+    def _over_every(*args: P.args, **kwargs: P.kwargs) -> bool:
+        return all(func(*args, **kwargs) for func in funcs)
+
+    return _over_every
+
+
+def over_some(funcs: t.Iterable[t.Callable[P, t.Any]]) -> t.Callable[P, bool]:
+    """
+    Creates a function that checks if any of the functions in `funcs` return truthy when invoked
+    with the arguments it receives.
+
+    Args:
+        funcs: List of functions to be invoked.
+
+    Returns:
+        Returns the new pass-thru function.
+
+    Example:
+
+        >>> func = over_some([bool, lambda x: x is None])
+        >>> func(1)
+        True
+
+    .. versionadded:: 4.0.0
+    """
+
+    def _over_some(*args: P.args, **kwargs: P.kwargs) -> bool:
+        return any(func(*args, **kwargs) for func in funcs)
+
+    return _over_some
+
+
+def property_(path: PathT) -> t.Callable[[t.Any], t.Any]:
+    """
+    Creates a function that returns the value at path of a given object.
+
+    Args:
+        path: Path value to fetch from object.
+
+    Returns:
+        Function that returns object's path value.
+
+    Example:
+
+        >>> get_data = property_("data")
+        >>> get_data({"data": 1})
+        1
+        >>> get_data({}) is None
+        True
+        >>> get_first = property_(0)
+        >>> get_first([1, 2, 3])
+        1
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 4.0.1
+        Made property accessor work with deep path strings.
+    """
+    return lambda obj: pyd.get(obj, path)
+
+
+def properties(*paths: t.Any) -> t.Callable[[t.Any], t.Any]:
+    """
+    Like :func:`property_` except that it returns a list of values at each path in `paths`.
+
+    Args:
+        *path: Path values to fetch from object.
+
+    Returns:
+        Function that returns object's path value.
+
+    Example:
+
+        >>> getter = properties("a", "b", ["c", "d", "e"])
+        >>> getter({"a": 1, "b": 2, "c": {"d": {"e": 3}}})
+        [1, 2, 3]
+
+    .. versionadded:: 4.1.0
+    """
+    return lambda obj: [getter(obj) for getter in (pyd.property_(path) for path in paths)]
+
+
+def property_of(obj: t.Any) -> t.Callable[[PathT], t.Any]:
+    """
+    The inverse of :func:`property_`. This method creates a function that returns the key value of a
+    given key on `obj`.
+
+    Args:
+        obj: Object to fetch values from.
+
+    Returns:
+        Function that returns object's key value.
+
+    Example:
+
+        >>> getter = property_of({"a": 1, "b": 2, "c": 3})
+        >>> getter("a")
+        1
+        >>> getter("b")
+        2
+        >>> getter("x") is None
+        True
+
+    .. versionadded:: 3.0.0
+
+    .. versionchanged:: 4.0.0
+        Removed alias ``prop_of``.
+    """
+    return lambda key: pyd.get(obj, key)
+
+
+@t.overload
+def random(start: int = 0, stop: int = 1, *, floating: Literal[False] = False) -> int: ...
+
+
+@t.overload
+def random(start: float, stop: int = 1, floating: bool = False) -> float: ...
+
+
+@t.overload
+def random(start: int = 0, *, stop: float, floating: bool = False) -> float: ...
+
+
+@t.overload
+def random(start: float, stop: float, floating: bool = False) -> float: ...
+
+
+@t.overload
+def random(
+    start: t.Union[float, int] = 0, stop: t.Union[float, int] = 1, *, floating: Literal[True]
+) -> float: ...
+
+
+def random(start: t.Union[float, int] = 0, stop: t.Union[float, int] = 1, floating: bool = False):
+    """
+    Produces a random number between `start` and `stop` (inclusive). If only one argument is
+    provided a number between 0 and the given number will be returned. If floating is truthy or
+    either `start` or `stop` are floats a floating-point number will be returned instead of an
+    integer.
+
+    Args:
+        start: Minimum value.
+        stop: Maximum value.
+        floating: Whether to force random value to ``float``. Defaults to
+            ``False``.
+
+    Returns:
+        Random value.
+
+    Example:
+
+        >>> 0 <= random() <= 1
+        True
+        >>> 5 <= random(5, 10) <= 10
+        True
+        >>> isinstance(random(floating=True), float)
+        True
+
+    .. versionadded:: 1.0.0
+    """
+    floating = isinstance(start, float) or isinstance(stop, float) or floating is True
+
+    if stop < start:
+        stop, start = start, stop
+
+    if floating:
+        rnd = uniform(start, stop)
+    else:
+        rnd = randint(start, stop)  # type: ignore
+
+    return rnd
+
+
+@t.overload
+def range_(stop: int) -> t.Generator[int, None, None]: ...
+
+
+@t.overload
+def range_(start: int, stop: int, step: int = 1) -> t.Generator[int, None, None]: ...
+
+
+def range_(*args):
+    """
+    Creates a list of numbers (positive and/or negative) progressing from start up to but not
+    including end. If `start` is less than `stop`, a zero-length range is created unless a negative
+    `step` is specified.
+
+    Args:
+        start: Integer to start with. Defaults to ``0``.
+        stop: Integer to stop at.
+        step: The value to increment or decrement by. Defaults to ``1``.
+
+    Yields:
+        Next integer in range.
+
+    Example:
+
+        >>> list(range_(5))
+        [0, 1, 2, 3, 4]
+        >>> list(range_(1, 4))
+        [1, 2, 3]
+        >>> list(range_(0, 6, 2))
+        [0, 2, 4]
+        >>> list(range_(4, 1))
+        [4, 3, 2]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 1.1.0
+        Moved to :mod:`pydash.uilities`.
+
+    .. versionchanged:: 3.0.0
+        Return generator instead of list.
+
+    .. versionchanged:: 4.0.0
+        Support decrementing when start argument is greater than stop argument.
+    """
+    return base_range(*args)
+
+
+@t.overload
+def range_right(stop: int) -> t.Generator[int, None, None]: ...
+
+
+@t.overload
+def range_right(start: int, stop: int, step: int = 1) -> t.Generator[int, None, None]: ...
+
+
+def range_right(*args):
+    """
+    Similar to :func:`range_`, except that it populates the values in descending order.
+
+    Args:
+        start: Integer to start with. Defaults to ``0``.
+        stop: Integer to stop at.
+        step: The value to increment or decrement by. Defaults to ``1`` if `start`
+            < `stop` else ``-1``.
+
+    Yields:
+        Next integer in range.
+
+    Example:
+
+        >>> list(range_right(5))
+        [4, 3, 2, 1, 0]
+        >>> list(range_right(1, 4))
+        [3, 2, 1]
+        >>> list(range_right(0, 6, 2))
+        [4, 2, 0]
+
+    .. versionadded:: 4.0.0
+    """
+    return base_range(*args, from_right=True)
+
+
+# TODO
+@t.overload
+def result(obj: None, key: t.Any, default: None = None) -> None: ...
+
+
+@t.overload
+def result(obj: None, key: t.Any, default: T) -> T: ...
+
+
+@t.overload
+def result(obj: t.Any, key: t.Any, default: t.Any = None) -> t.Any: ...
+
+
+def result(obj, key, default=None):
+    """
+    Return the value of property `key` on `obj`. If `key` value is a function it will be invoked and
+    its result returned, else the property value is returned. If `obj` is falsey then `default` is
+    returned.
+
+    Args:
+        obj: Object to retrieve result from.
+        key: Key or index to get result from.
+        default: Default value to return if `obj` is falsey. Defaults to ``None``.
+
+    Returns:
+        Result of ``obj[key]`` or ``None``.
+
+    Example:
+
+        >>> result({"a": 1, "b": lambda: 2}, "a")
+        1
+        >>> result({"a": 1, "b": lambda: 2}, "b")
+        2
+        >>> result({"a": 1, "b": lambda: 2}, "c") is None
+        True
+        >>> result({"a": 1, "b": lambda: 2}, "c", default=False)
+        False
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 2.0.0
+        Added ``default`` argument.
+    """
+    if not obj:
+        return default
+
+    ret = base_get(obj, key, default=default)
+
+    if callable(ret):
+        ret = ret()
+
+    return ret
+
+
+def retry(
+    attempts: int = 3,
+    delay: t.Union[int, float] = 0.5,
+    max_delay: t.Union[int, float] = 150.0,
+    scale: t.Union[int, float] = 2.0,
+    jitter: t.Union[int, float, t.Tuple[t.Union[int, float], t.Union[int, float]]] = 0,
+    exceptions: t.Iterable[Type[Exception]] = (Exception,),
+    on_exception: t.Union[t.Callable[[Exception, int], t.Any], None] = None,
+) -> t.Callable[[CallableT], CallableT]:
+    """
+    Decorator that retries a function multiple times if it raises an exception with an optional
+    delay between each attempt.
+
+    When a `delay` is supplied, there will be a sleep period in between retry
+    attempts. The first delay time will always be equal to `delay`. After
+    subsequent retries, the delay time will be scaled by `scale` up to
+    `max_delay`. If `max_delay` is ``0``, then `delay` can increase unbounded.
+
+    Args:
+        attempts: Number of retry attempts. Defaults to ``3``.
+        delay: Base amount of seconds to sleep between retry attempts.
+            Defaults to ``0.5``.
+        max_delay: Maximum number of seconds to sleep between retries. Is
+            ignored when equal to ``0``. Defaults to ``150.0`` (2.5 minutes).
+        scale: Scale factor to increase `delay` after first retry fails.
+            Defaults to ``2.0``.
+        jitter: Random jitter to add to `delay` time. Can be a positive
+            number or 2-item tuple of numbers representing the random range to choose from. When a
+            number is given, the random range will be from ``[0, jitter]``. When jitter is a float
+            or contains a float, then a random float will be chosen; otherwise, a random integer
+            will be selected. Defaults to ``0`` which disables jitter.
+        exceptions: Tuple of exceptions that trigger a retry attempt. Exceptions
+            not in the tuple will be ignored. Defaults to ``(Exception,)`` (all exceptions).
+        on_exception: Function that is called when a retryable exception is
+            caught. It is invoked with ``on_exception(exc, attempt)`` where ``exc`` is the caught
+            exception and ``attempt`` is the attempt count. All arguments are optional. Defaults to
+            ``None``.
+
+    Example:
+
+        >>> @retry(attempts=3, delay=0)
+        ... def do_something():
+        ...     print("something")
+        ...     raise Exception("something went wrong")
+        >>> try:
+        ...     do_something()
+        ... except Exception:
+        ...     print("caught something")
+        something
+        something
+        something
+        caught something
+
+    ..versionadded:: 4.4.0
+
+    ..versionchanged:: 4.5.0
+        Added ``jitter`` argument.
+    """
+    if not isinstance(attempts, int) or attempts <= 0:
+        raise ValueError("attempts must be an integer greater than 0")
+
+    if not isinstance(delay, NUMBER_TYPES) or delay < 0:
+        raise ValueError("delay must be a number greater than or equal to 0")
+
+    if not isinstance(max_delay, NUMBER_TYPES) or max_delay < 0:
+        raise ValueError("scale must be a number greater than or equal to 0")
+
+    if not isinstance(scale, NUMBER_TYPES) or scale <= 0:
+        raise ValueError("scale must be a number greater than 0")
+
+    if (
+        not isinstance(jitter, NUMBER_TYPES + (tuple,))
+        or (isinstance(jitter, NUMBER_TYPES) and jitter < 0)
+        or (
+            isinstance(jitter, tuple)
+            and (len(jitter) != 2 or not all(isinstance(jit, NUMBER_TYPES) for jit in jitter))
+        )
+    ):
+        raise ValueError("jitter must be a number greater than 0 or a 2-item tuple of numbers")
+
+    if not isinstance(exceptions, tuple) or not all(
+        issubclass(exc, Exception) for exc in exceptions
+    ):
+        raise TypeError("exceptions must be a tuple of Exception types")
+
+    if on_exception and not callable(on_exception):
+        raise TypeError("on_exception must be a callable")
+
+    if jitter and not isinstance(jitter, tuple):
+        jitter = (0, jitter)
+
+    on_exc_argcount = getargcount(on_exception, maxargs=2) if on_exception else None
+
+    def decorator(func):
+        @wraps(func)
+        def decorated(*args, **kwargs):
+            delay_time = delay
+
+            for attempt in range(1, attempts + 1):
+                # pylint: disable=catching-non-exception
+                try:
+                    return func(*args, **kwargs)
+                except exceptions as exc:
+                    if on_exception:
+                        callit(on_exception, exc, attempt, argcount=on_exc_argcount)
+
+                    if attempt == attempts:
+                        raise
+
+                    if jitter:
+                        delay_time += max(0, random(*jitter))
+
+                    if delay_time < 0:  # pragma: no cover
+                        continue
+
+                    if max_delay:
+                        delay_time = min(delay_time, max_delay)
+
+                    time.sleep(delay_time)
+
+                    # Scale after first iteration.
+                    delay_time *= scale
+
+        return decorated
+
+    return decorator
+
+
+def stub_list() -> t.List[t.Any]:
+    """
+    Returns empty "list".
+
+    Returns:
+        Empty list.
+
+    Example:
+
+        >>> stub_list()
+        []
+
+    .. versionadded:: 4.0.0
+    """
+    return []
+
+
+def stub_dict() -> t.Dict[t.Any, t.Any]:
+    """
+    Returns empty "dict".
+
+    Returns:
+        Empty dict.
+
+    Example:
+
+        >>> stub_dict()
+        {}
+
+    .. versionadded:: 4.0.0
+    """
+    return {}
+
+
+def stub_false() -> Literal[False]:
+    """
+    Returns ``False``.
+
+    Returns:
+        False
+
+    Example:
+
+        >>> stub_false()
+        False
+
+    .. versionadded:: 4.0.0
+    """
+    return False
+
+
+def stub_string() -> str:
+    """
+    Returns an empty string.
+
+    Returns:
+        Empty string
+
+    Example:
+
+        >>> stub_string()
+        ''
+
+    .. versionadded:: 4.0.0
+    """
+    return ""
+
+
+def stub_true() -> Literal[True]:
+    """
+    Returns ``True``.
+
+    Returns:
+        True
+
+    Example:
+
+        >>> stub_true()
+        True
+
+    .. versionadded:: 4.0.0
+    """
+    return True
+
+
+@t.overload
+def times(n: int, iteratee: t.Callable[..., T]) -> t.List[T]: ...
+
+
+@t.overload
+def times(n: int, iteratee: None = None) -> t.List[int]: ...
+
+
+def times(n: int, iteratee=None):
+    """
+    Executes the iteratee `n` times, returning a list of the results of each iteratee execution. The
+    iteratee is invoked with one argument: ``(index)``.
+
+    Args:
+        n: Number of times to execute `iteratee`.
+        iteratee: Function to execute.
+
+    Returns:
+        A list of results from calling `iteratee`.
+
+    Example:
+
+        >>> times(5, lambda i: i)
+        [0, 1, 2, 3, 4]
+
+    .. versionadded:: 1.0.0
+
+    .. versionchanged:: 3.0.0
+        Reordered arguments to make `iteratee` first.
+
+    .. versionchanged:: 4.0.0
+
+        - Re-reordered arguments to make `iteratee` last argument.
+        - Added functionality for handling `iteratee` with zero positional arguments.
+    """
+    if iteratee is None:
+        iteratee = identity
+        argcount = 1
+    else:
+        argcount = getargcount(iteratee, maxargs=1)
+
+    return [callit(iteratee, index, argcount=argcount) for index in range(n)]
+
+
+def to_path(value: PathT) -> t.List[t.Hashable]:
+    """
+    Converts values to a property path array.
+
+    Args:
+        value: Value to convert.
+
+    Returns:
+        Returns the new property path array.
+
+    Example:
+
+        >>> to_path("a.b.c")
+        ['a', 'b', 'c']
+        >>> to_path("a[0].b.c")
+        ['a', 0, 'b', 'c']
+        >>> to_path("a[0][1][2].b.c")
+        ['a', 0, 1, 2, 'b', 'c']
+
+    .. versionadded:: 4.0.0
+
+    .. versionchanged:: 4.2.1
+        Ensure returned path is always a list.
+    """
+    path = [token.key for token in to_path_tokens(value)]
+    return path
+
+
+def unique_id(prefix: t.Union[str, None] = None) -> str:
+    """
+    Generates a unique ID. If `prefix` is provided the ID will be appended to  it.
+
+    Args:
+        prefix: String prefix to prepend to ID value.
+
+    Returns:
+        ID value.
+
+    Example:
+
+        >>> unique_id()
+        '1'
+        >>> unique_id("id_")
+        'id_2'
+        >>> unique_id()
+        '3'
+
+    .. versionadded:: 1.0.0
+    """
+    # pylint: disable=global-statement
+    global ID_COUNTER  # noqa: PLW0603
+    ID_COUNTER += 1
+
+    if prefix is None:
+        prefix = ""
+    else:
+        prefix = pyd.to_string(prefix)
+    return f"{prefix}{ID_COUNTER}"
+
+
+#
+# Helper functions not a part of main API
+#
+
+
+def _maybe_list_index(key):
+    if isinstance(key, int):
+        return key
+    if pyd.is_string(key) and RE_PATH_LIST_INDEX.match(key):
+        return int(key[1:-1])
+    return None
+
+
+def _to_path_token(key) -> PathToken:
+    list_index = _maybe_list_index(key)
+    if list_index is not None:
+        return PathToken(list_index, default_factory=list)
+    return PathToken(
+        unescape_path_key(key) if pyd.is_string(key) else key,
+        default_factory=dict,
+    )
+
+
+def to_path_tokens(value) -> t.List[PathToken]:
+    """Parse `value` into :class:`PathToken` objects."""
+    if pyd.is_string(value) and ("." in value or "[" in value):
+        # Since we can't tell whether a bare number is supposed to be dict key or a list index, we
+        # support a special syntax where any string-integer surrounded by brackets is treated as a
+        # list index and converted to an integer.
+        keys = [_to_path_token(key) for key in filter(None, RE_PATH_KEY_DELIM.split(value))]
+    elif pyd.is_string(value) or pyd.is_number(value):
+        keys = [PathToken(value, default_factory=dict)]
+    elif value is UNSET:
+        keys = []
+    elif pyd.is_list(value):
+        keys = [_to_path_token(key) for key in value]
+    else:
+        keys = [_to_path_token(value)]
+
+    return keys
+
+
+def unescape_path_key(key):
+    """Unescape path key."""
+    key = key.replace(r"\\", "\\")
+    key = key.replace(r"\.", r".")
+    return key
+
+
+def base_range(*args, **kwargs):
+    """Yield range values."""
+    from_right = kwargs.get("from_right", False)
+
+    if len(args) >= 3:
+        args = args[:3]
+    elif len(args) == 2:
+        args = (args[0], args[1], None)
+    elif len(args) == 1:
+        args = (0, args[0], None)
+
+    if args and args[2] is None:
+        check_args = args[:2]
+    else:
+        check_args = args
+
+    for arg in check_args:
+        if not isinstance(arg, int):  # pragma: no cover
+            raise TypeError(f"range cannot interpret {type(arg).__name__!r} object as an integer")
+
+    def gen():
+        if not args:
+            return
+
+        start, stop, step = args
+
+        if step is None:
+            step = 1 if start < stop else -1
+
+        length = int(max([math.ceil((stop - start) / (step or 1)), 0]))
+
+        if from_right:
+            start += (step * length) - step
+            step *= -1
+
+        while length:
+            yield start
+
+            start += step
+            length -= 1
+
+    return gen()