Repository: pandas-dev/pandas
Pull request #56561 (2023-12-19 13:34:39)

DOC: Add example using ``dtype_backend="numpy_nullable"`` to ``read_json``
- [x] [closes](https://github.com/noatamir/pyladies-berlin-sprints/issues/4)


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 782340178f..42ff96603b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -132,11 +132,11 @@ repos:
         types: [python]
         stages: [manual]
         additional_dependencies: &pyright_dependencies
-        - pyright@1.1.318
+        - pyright@1.1.339
     -   id: pyright
         # note: assumes python env is setup and activated
         name: pyright reportGeneralTypeIssues
-        entry: pyright --skipunannotated -p pyright_reportGeneralTypeIssues.json --level warning
+        entry: pyright -p pyright_reportGeneralTypeIssues.json --level warning
         language: node
         pass_filenames: false
         types: [python]
diff --git a/doc/source/reference/groupby.rst b/doc/source/reference/groupby.rst
index fe02fa106f..771163ae1b 100644
--- a/doc/source/reference/groupby.rst
+++ b/doc/source/reference/groupby.rst
@@ -5,7 +5,7 @@
 =======
 GroupBy
 =======
-.. currentmodule:: pandas.api.typing
+.. currentmodule:: pandas.core.groupby
 
 :class:`pandas.api.typing.DataFrameGroupBy` and :class:`pandas.api.typing.SeriesGroupBy`
 instances are returned by groupby calls :func:`pandas.DataFrame.groupby` and
@@ -40,7 +40,7 @@ Function application helper
 
    NamedAgg
 
-.. currentmodule:: pandas.api.typing
+.. currentmodule:: pandas.core.groupby
 
 Function application
 --------------------
diff --git a/doc/source/reference/resampling.rst b/doc/source/reference/resampling.rst
index 2b52087cec..edbc8090fc 100644
--- a/doc/source/reference/resampling.rst
+++ b/doc/source/reference/resampling.rst
@@ -5,7 +5,7 @@
 ==========
 Resampling
 ==========
-.. currentmodule:: pandas.api.typing
+.. currentmodule:: pandas.core.resample
 
 :class:`pandas.api.typing.Resampler` instances are returned by
 resample calls: :func:`pandas.DataFrame.resample`, :func:`pandas.Series.resample`.
diff --git a/doc/source/reference/window.rst b/doc/source/reference/window.rst
index 75faf69583..14af2b8a12 100644
--- a/doc/source/reference/window.rst
+++ b/doc/source/reference/window.rst
@@ -17,7 +17,7 @@ calls: :func:`pandas.DataFrame.ewm` and :func:`pandas.Series.ewm`.
 
 Rolling window functions
 ------------------------
-.. currentmodule:: pandas.api.typing
+.. currentmodule:: pandas.core.window.rolling
 
 .. autosummary::
    :toctree: api/
@@ -44,7 +44,7 @@ Rolling window functions
 
 Weighted window functions
 -------------------------
-.. currentmodule:: pandas.api.typing
+.. currentmodule:: pandas.core.window.rolling
 
 .. autosummary::
    :toctree: api/
@@ -58,7 +58,7 @@ Weighted window functions
 
 Expanding window functions
 --------------------------
-.. currentmodule:: pandas.api.typing
+.. currentmodule:: pandas.core.window.expanding
 
 .. autosummary::
    :toctree: api/
@@ -85,7 +85,7 @@ Expanding window functions
 
 Exponentially-weighted window functions
 ---------------------------------------
-.. currentmodule:: pandas.api.typing
+.. currentmodule:: pandas.core.window.ewm
 
 .. autosummary::
    :toctree: api/
diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst
index 3a11e5e4e7..081f822069 100644
--- a/doc/source/whatsnew/v2.2.0.rst
+++ b/doc/source/whatsnew/v2.2.0.rst
@@ -229,6 +229,7 @@ Other enhancements
 - :func:`tseries.api.guess_datetime_format` is now part of the public API (:issue:`54727`)
 - :meth:`ExtensionArray._explode` interface method added to allow extension type implementations of the ``explode`` method (:issue:`54833`)
 - :meth:`ExtensionArray.duplicated` added to allow extension type implementations of the ``duplicated`` method (:issue:`55255`)
+- :meth:`Series.ffill`, :meth:`Series.bfill`, :meth:`DataFrame.ffill`, and :meth:`DataFrame.bfill` have gained the argument ``limit_area`` (:issue:`56492`)
 - Allow passing ``read_only``, ``data_only`` and ``keep_links`` arguments to openpyxl using ``engine_kwargs`` of :func:`read_excel` (:issue:`55027`)
 - DataFrame.apply now allows the usage of numba (via ``engine="numba"``) to JIT compile the passed function, allowing for potential speedups (:issue:`54666`)
 - Implement masked algorithms for :meth:`Series.value_counts` (:issue:`54984`)
@@ -345,6 +346,8 @@ Optional libraries below the lowest tested version may still work, but are not c
 +-----------------+-----------------+---------+
 | Package         | Minimum Version | Changed |
 +=================+=================+=========+
+| mypy (dev)      | 1.7.1           |    X    |
++-----------------+-----------------+---------+
 |                 |                 |    X    |
 +-----------------+-----------------+---------+
 
diff --git a/environment.yml b/environment.yml
index 0014e71ed0..5fad8b5031 100644
--- a/environment.yml
+++ b/environment.yml
@@ -76,7 +76,7 @@ dependencies:
 
   # code checks
   - flake8=6.1.0  # run in subprocess over docstring examples
-  - mypy=1.4.1  # pre-commit uses locally installed mypy
+  - mypy=1.7.1  # pre-commit uses locally installed mypy
   - tokenize-rt  # scripts/check_for_inconsistent_pandas_namespace.py
   - pre-commit>=3.6.0
 
diff --git a/pandas/_config/config.py b/pandas/_config/config.py
index a776825732..c391939d22 100644
--- a/pandas/_config/config.py
+++ b/pandas/_config/config.py
@@ -220,6 +220,8 @@ def get_default_val(pat: str):
 class DictWrapper:
     """provide attribute-style access to a nested dict"""
 
+    d: dict[str, Any]
+
     def __init__(self, d: dict[str, Any], prefix: str = "") -> None:
         object.__setattr__(self, "d", d)
         object.__setattr__(self, "prefix", prefix)
@@ -250,7 +252,7 @@ class DictWrapper:
         else:
             return _get_option(prefix)
 
-    def __dir__(self) -> Iterable[str]:
+    def __dir__(self) -> list[str]:
         return list(self.d.keys())
 
 
diff --git a/pandas/_libs/tslibs/nattype.pyi b/pandas/_libs/tslibs/nattype.pyi
index 437b5ab667..bd86a6fdc2 100644
--- a/pandas/_libs/tslibs/nattype.pyi
+++ b/pandas/_libs/tslibs/nattype.pyi
@@ -8,6 +8,7 @@ import typing
 import numpy as np
 
 from pandas._libs.tslibs.period import Period
+from pandas._typing import Self
 
 NaT: NaTType
 iNaT: int
@@ -132,4 +133,9 @@ class NaTType:
     __le__: _NatComparison
     __gt__: _NatComparison
     __ge__: _NatComparison
+    def __sub__(self, other: Self | timedelta | datetime) -> Self: ...
+    def __rsub__(self, other: Self | timedelta | datetime) -> Self: ...
+    def __add__(self, other: Self | timedelta | datetime) -> Self: ...
+    def __radd__(self, other: Self | timedelta | datetime) -> Self: ...
+    def __hash__(self) -> int: ...
     def as_unit(self, unit: str, round_ok: bool = ...) -> NaTType: ...
diff --git a/pandas/_libs/tslibs/timestamps.pyi b/pandas/_libs/tslibs/timestamps.pyi
index c78c174e27..c769b09d1b 100644
--- a/pandas/_libs/tslibs/timestamps.pyi
+++ b/pandas/_libs/tslibs/timestamps.pyi
@@ -10,7 +10,6 @@ from typing import (
     ClassVar,
     Literal,
     TypeAlias,
-    TypeVar,
     overload,
 )
 
@@ -28,7 +27,6 @@ from pandas._typing import (
     TimestampNonexistent,
 )
 
-_DatetimeT = TypeVar("_DatetimeT", bound=datetime)
 _TimeZones: TypeAlias = str | _tzinfo | None | int
 
 def integer_op_not_supported(obj: object) -> TypeError: ...
@@ -42,7 +40,7 @@ class Timestamp(datetime):
     _value: int  # np.int64
     # error: "__new__" must return a class instance (got "Union[Timestamp, NaTType]")
     def __new__(  # type: ignore[misc]
-        cls: type[_DatetimeT],
+        cls: type[Self],
         ts_input: np.integer | float | str | _date | datetime | np.datetime64 = ...,
         year: int | None = ...,
         month: int | None = ...,
@@ -57,7 +55,7 @@ class Timestamp(datetime):
         tz: _TimeZones = ...,
         unit: str | int | None = ...,
         fold: int | None = ...,
-    ) -> _DatetimeT | NaTType: ...
+    ) -> Self | NaTType: ...
     @classmethod
     def _from_value_and_reso(
         cls, value: int, reso: int, tz: _TimeZones
diff --git a/pandas/_typing.py b/pandas/_typing.py
index c2d51f63eb..3df9a47a35 100644
--- a/pandas/_typing.py
+++ b/pandas/_typing.py
@@ -234,7 +234,9 @@ IndexKeyFunc = Optional[Callable[["Index"], Union["Index", AnyArrayLike]]]
 
 # types of `func` kwarg for DataFrame.aggregate and Series.aggregate
 AggFuncTypeBase = Union[Callable, str]
-AggFuncTypeDict = dict[Hashable, Union[AggFuncTypeBase, list[AggFuncTypeBase]]]
+AggFuncTypeDict = MutableMapping[
+    Hashable, Union[AggFuncTypeBase, list[AggFuncTypeBase]]
+]
 AggFuncType = Union[
     AggFuncTypeBase,
     list[AggFuncTypeBase],
diff --git a/pandas/core/_numba/kernels/var_.py b/pandas/core/_numba/kernels/var_.py
index e150c719b8..c63d0b90b0 100644
--- a/pandas/core/_numba/kernels/var_.py
+++ b/pandas/core/_numba/kernels/var_.py
@@ -116,7 +116,7 @@ def sliding_var(
                     ssqdm_x,
                     compensation_add,
                     num_consecutive_same_value,
-                    prev_value,  # pyright: ignore[reportGeneralTypeIssues]
+                    prev_value,
                 )
         else:
             for j in range(start[i - 1], s):
@@ -141,7 +141,7 @@ def sliding_var(
                     ssqdm_x,
                     compensation_add,
                     num_consecutive_same_value,
-                    prev_value,  # pyright: ignore[reportGeneralTypeIssues]
+                    prev_value,
                 )
 
         if nobs >= min_periods and nobs > ddof:
diff --git a/pandas/core/apply.py b/pandas/core/apply.py
index 169a44accf..25a71ce5b5 100644
--- a/pandas/core/apply.py
+++ b/pandas/core/apply.py
@@ -9,7 +9,6 @@ from typing import (
     TYPE_CHECKING,
     Any,
     Callable,
-    DefaultDict,
     Literal,
     cast,
 )
@@ -63,6 +62,7 @@ if TYPE_CHECKING:
         Generator,
         Hashable,
         Iterable,
+        MutableMapping,
         Sequence,
     )
 
@@ -1642,7 +1642,7 @@ class ResamplerWindowApply(GroupByApply):
 
 def reconstruct_func(
     func: AggFuncType | None, **kwargs
-) -> tuple[bool, AggFuncType, list[str] | None, npt.NDArray[np.intp] | None]:
+) -> tuple[bool, AggFuncType, tuple[str, ...] | None, npt.NDArray[np.intp] | None]:
     """
     This is the internal function to reconstruct func given if there is relabeling
     or not and also normalize the keyword to get new order of columns.
@@ -1668,7 +1668,7 @@ def reconstruct_func(
     -------
     relabelling: bool, if there is relabelling or not
     func: normalized and mangled func
-    columns: list of column names
+    columns: tuple of column names
     order: array of columns indices
 
     Examples
@@ -1680,7 +1680,7 @@ def reconstruct_func(
     (False, 'min', None, None)
     """
     relabeling = func is None and is_multi_agg_with_relabel(**kwargs)
-    columns: list[str] | None = None
+    columns: tuple[str, ...] | None = None
     order: npt.NDArray[np.intp] | None = None
 
     if not relabeling:
@@ -1696,7 +1696,14 @@ def reconstruct_func(
             raise TypeError("Must provide 'func' or tuples of '(column, aggfunc).")
 
     if relabeling:
-        func, columns, order = normalize_keyword_aggregation(kwargs)
+        # error: Incompatible types in assignment (expression has type
+        # "MutableMapping[Hashable, list[Callable[..., Any] | str]]", variable has type
+        # "Callable[..., Any] | str | list[Callable[..., Any] | str] |
+        # MutableMapping[Hashable, Callable[..., Any] | str | list[Callable[..., Any] |
+        # str]] | None")
+        func, columns, order = normalize_keyword_aggregation(  # type: ignore[assignment]
+            kwargs
+        )
     assert func is not None
 
     return relabeling, func, columns, order
@@ -1730,7 +1737,11 @@ def is_multi_agg_with_relabel(**kwargs) -> bool:
 
 def normalize_keyword_aggregation(
     kwargs: dict,
-) -> tuple[dict, list[str], npt.NDArray[np.intp]]:
+) -> tuple[
+    MutableMapping[Hashable, list[AggFuncTypeBase]],
+    tuple[str, ...],
+    npt.NDArray[np.intp],
+]:
     """
     Normalize user-provided "named aggregation" kwargs.
     Transforms from the new ``Mapping[str, NamedAgg]`` style kwargs
@@ -1744,7 +1755,7 @@ def normalize_keyword_aggregation(
     -------
     aggspec : dict
         The transformed kwargs.
-    columns : List[str]
+    columns : tuple[str, ...]
         The user-provided keys.
     col_idx_order : List[int]
         List of columns indices.
@@ -1759,9 +1770,7 @@ def normalize_keyword_aggregation(
     # Normalize the aggregation functions as Mapping[column, List[func]],
     # process normally, then fixup the names.
     # TODO: aggspec type: typing.Dict[str, List[AggScalar]]
-    # May be hitting https://github.com/python/mypy/issues/5958
-    # saying it doesn't have an attribute __name__
-    aggspec: DefaultDict = defaultdict(list)
+    aggspec = defaultdict(list)
     order = []
     columns, pairs = list(zip(*kwargs.items()))
 
diff --git a/pandas/core/arrays/_ranges.py b/pandas/core/arrays/_ranges.py
index 5f0409188e..6a1ef08003 100644
--- a/pandas/core/arrays/_ranges.py
+++ b/pandas/core/arrays/_ranges.py
@@ -57,9 +57,7 @@ def generate_regular_range(
     b: int | np.int64 | np.uint64
     e: int | np.int64 | np.uint64
     try:
-        td = td.as_unit(  # pyright: ignore[reportGeneralTypeIssues]
-            unit, round_ok=False
-        )
+        td = td.as_unit(unit, round_ok=False)
     except ValueError as err:
         raise ValueError(
             f"freq={freq} is incompatible with unit={unit}. "
diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py
index 20aec52b60..065a942cae 100644
--- a/pandas/core/arrays/categorical.py
+++ b/pandas/core/arrays/categorical.py
@@ -2894,9 +2894,7 @@ class CategoricalAccessor(PandasDelegate, PandasObject, NoNewAttributesMixin):
         if not isinstance(data.dtype, CategoricalDtype):
             raise AttributeError("Can only use .cat accessor with a 'category' dtype")
 
-    # error: Signature of "_delegate_property_get" incompatible with supertype
-    # "PandasDelegate"
-    def _delegate_property_get(self, name: str):  # type: ignore[override]
+    def _delegate_property_get(self, name: str):
         return getattr(self._parent, name)
 
     # error: Signature of "_delegate_property_set" incompatible with supertype
diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py
index 91dd40c2de..1530fae89a 100644
--- a/pandas/core/arrays/datetimelike.py
+++ b/pandas/core/arrays/datetimelike.py
@@ -600,7 +600,9 @@ class DatetimeLikeArrayMixin(  # type: ignore[misc]
             raise TypeError(msg)
 
         elif isinstance(value, self._recognized_scalars):
-            value = self._scalar_type(value)
+            # error: Argument 1 to "Timestamp" has incompatible type "object"; expected
+            # "integer[Any] | float | str | date | datetime | datetime64"
+            value = self._scalar_type(value)  # type: ignore[arg-type]
 
         else:
             msg = self._validation_error_message(value, allow_listlike)
diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py
index 0074645a48..9329f82c1b 100644
--- a/pandas/core/arrays/datetimes.py
+++ b/pandas/core/arrays/datetimes.py
@@ -397,10 +397,8 @@ class DatetimeArray(dtl.TimelikeOps, dtl.DatelikeOps):  # type: ignore[misc]
         result._maybe_pin_freq(freq, validate_kwds)
         return result
 
-    # error: Signature of "_generate_range" incompatible with supertype
-    # "DatetimeLikeArrayMixin"
     @classmethod
-    def _generate_range(  # type: ignore[override]
+    def _generate_range(
         cls,
         start,
         end,
diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py
index ccb63d6677..95c3c641fd 100644
--- a/pandas/core/arrays/timedeltas.py
+++ b/pandas/core/arrays/timedeltas.py
@@ -267,10 +267,8 @@ class TimedeltaArray(dtl.TimelikeOps):
         result._maybe_pin_freq(freq, {})
         return result
 
-    # Signature of "_generate_range" incompatible with supertype
-    # "DatetimeLikeArrayMixin"
     @classmethod
-    def _generate_range(  # type: ignore[override]
+    def _generate_range(
         cls, start, end, periods, freq, closed=None, *, unit: str | None = None
     ) -> Self:
         periods = dtl.validate_periods(periods)
diff --git a/pandas/core/base.py b/pandas/core/base.py
index 9c722bad01..e98f115757 100644
--- a/pandas/core/base.py
+++ b/pandas/core/base.py
@@ -1310,12 +1310,10 @@ class IndexOpsMixin(OpsMixin):
     # This overload is needed so that the call to searchsorted in
     # pandas.core.resample.TimeGrouper._get_period_bins picks the correct result
 
-    @overload
-    # The following ignore is also present in numpy/__init__.pyi
-    # Possibly a mypy bug??
     # error: Overloaded function signatures 1 and 2 overlap with incompatible
-    # return types  [misc]
-    def searchsorted(  # type: ignore[misc]
+    # return types
+    @overload
+    def searchsorted(  # type: ignore[overload-overlap]
         self,
         value: ScalarLike_co,
         side: Literal["left", "right"] = ...,
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
index 794fcfb897..0d0dc476ee 100644
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -6896,14 +6896,7 @@ class DataFrame(NDFrame, OpsMixin):
             vals = (col.values for name, col in self.items() if name in subset)
             labels, shape = map(list, zip(*map(f, vals)))
 
-            ids = get_group_index(
-                labels,
-                # error: Argument 1 to "tuple" has incompatible type "List[_T]";
-                # expected "Iterable[int]"
-                tuple(shape),  # type: ignore[arg-type]
-                sort=False,
-                xnull=False,
-            )
+            ids = get_group_index(labels, tuple(shape), sort=False, xnull=False)
             result = self._constructor_sliced(duplicated(ids, keep), index=self.index)
         return result.__finalize__(self, method="duplicated")
 
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
index 22ac3f44d0..de25a02c6b 100644
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -7060,6 +7060,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
         axis: None | Axis = None,
         inplace: bool_t = False,
         limit: None | int = None,
+        limit_area: Literal["inside", "outside"] | None = None,
         downcast: dict | None = None,
     ):
         if axis is None:
@@ -7073,7 +7074,9 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
             #  in all axis=1 cases, and remove axis kward from mgr.pad_or_backfill.
             if inplace:
                 raise NotImplementedError()
-            result = self.T._pad_or_backfill(method=method, limit=limit).T
+            result = self.T._pad_or_backfill(
+                method=method, limit=limit, limit_area=limit_area
+            ).T
 
             return result
 
@@ -7081,6 +7084,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
             method=method,
             axis=self._get_block_manager_axis(axis),
             limit=limit,
+            limit_area=limit_area,
             inplace=inplace,
             downcast=downcast,
         )
@@ -7440,6 +7444,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
         axis: None | Axis = ...,
         inplace: Literal[False] = ...,
         limit: None | int = ...,
+        limit_area: Literal["inside", "outside"] | None = ...,
         downcast: dict | None | lib.NoDefault = ...,
     ) -> Self:
         ...
@@ -7451,6 +7456,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
         axis: None | Axis = ...,
         inplace: Literal[True],
         limit: None | int = ...,
+        limit_area: Literal["inside", "outside"] | None = ...,
         downcast: dict | None | lib.NoDefault = ...,
     ) -> None:
         ...
@@ -7462,6 +7468,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
         axis: None | Axis = ...,
         inplace: bool_t = ...,
         limit: None | int = ...,
+        limit_area: Literal["inside", "outside"] | None = ...,
         downcast: dict | None | lib.NoDefault = ...,
     ) -> Self | None:
         ...
@@ -7477,6 +7484,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
         axis: None | Axis = None,
         inplace: bool_t = False,
         limit: None | int = None,
+        limit_area: Literal["inside", "outside"] | None = None,
         downcast: dict | None | lib.NoDefault = lib.no_default,
     ) -> Self | None:
         """
@@ -7498,6 +7506,17 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
             be partially filled. If method is not specified, this is the
             maximum number of entries along the entire axis where NaNs will be
             filled. Must be greater than 0 if not None.
+        limit_area : {{`None`, 'inside', 'outside'}}, default None
+            If limit is specified, consecutive NaNs will be filled with this
+            restriction.
+
+            * ``None``: No fill restriction.
+            * 'inside': Only fill NaNs surrounded by valid values
+              (interpolate).
+            * 'outside': Only fill NaNs outside valid values (extrapolate).
+
+            .. versionadded:: 2.2.0
+
         downcast : dict, default is None
             A dict of item->dtype of what to downcast if possible,
             or the string 'infer' which will try to downcast to an appropriate
@@ -7569,6 +7588,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
             axis=axis,
             inplace=inplace,
             limit=limit,
+            limit_area=limit_area,
             # error: Argument "downcast" to "_fillna_with_method" of "NDFrame"
             # has incompatible type "Union[Dict[Any, Any], None,
             # Literal[_NoDefault.no_default]]"; expected "Optional[Dict[Any, Any]]"
@@ -7616,6 +7636,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
         axis: None | Axis = ...,
         inplace: Literal[False] = ...,
         limit: None | int = ...,
+        limit_area: Literal["inside", "outside"] | None = ...,
         downcast: dict | None | lib.NoDefault = ...,
     ) -> Self:
         ...
@@ -7638,6 +7659,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
         axis: None | Axis = ...,
         inplace: bool_t = ...,
         limit: None | int = ...,
+        limit_area: Literal["inside", "outside"] | None = ...,
         downcast: dict | None | lib.NoDefault = ...,
     ) -> Self | None:
         ...
@@ -7653,6 +7675,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
         axis: None | Axis = None,
         inplace: bool_t = False,
         limit: None | int = None,
+        limit_area: Literal["inside", "outside"] | None = None,
         downcast: dict | None | lib.NoDefault = lib.no_default,
     ) -> Self | None:
         """
@@ -7674,6 +7697,17 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
             be partially filled. If method is not specified, this is the
             maximum number of entries along the entire axis where NaNs will be
             filled. Must be greater than 0 if not None.
+        limit_area : {{`None`, 'inside', 'outside'}}, default None
+            If limit is specified, consecutive NaNs will be filled with this
+            restriction.
+
+            * ``None``: No fill restriction.
+            * 'inside': Only fill NaNs surrounded by valid values
+              (interpolate).
+            * 'outside': Only fill NaNs outside valid values (extrapolate).
+
+            .. versionadded:: 2.2.0
+
         downcast : dict, default is None
             A dict of item->dtype of what to downcast if possible,
             or the string 'infer' which will try to downcast to an appropriate
@@ -7756,6 +7790,7 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
             axis=axis,
             inplace=inplace,
             limit=limit,
+            limit_area=limit_area,
             # error: Argument "downcast" to "_fillna_with_method" of "NDFrame"
             # has incompatible type "Union[Dict[Any, Any], None,
             # Literal[_NoDefault.no_default]]"; expected "Optional[Dict[Any, Any]]"
@@ -7970,7 +8005,9 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
             if items:
                 keys, values = zip(*items)
             else:
-                keys, values = ([], [])
+                # error: Incompatible types in assignment (expression has type
+                # "list[Never]", variable has type "tuple[Any, ...]")
+                keys, values = ([], [])  # type: ignore[assignment]
 
             are_mappings = [is_dict_like(v) for v in values]
 
@@ -7985,7 +8022,12 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
                 value_dict = {}
 
                 for k, v in items:
-                    keys, values = list(zip(*v.items())) or ([], [])
+                    # error: Incompatible types in assignment (expression has type
+                    # "list[Never]", variable has type "tuple[Any, ...]")
+                    keys, values = list(zip(*v.items())) or (  # type: ignore[assignment]
+                        [],
+                        [],
+                    )
 
                     to_rep_dict[k] = list(keys)
                     value_dict[k] = list(values)
diff --git a/pandas/core/indexes/accessors.py b/pandas/core/indexes/accessors.py
index 10a3fcc61b..929c7f4a63 100644
--- a/pandas/core/indexes/accessors.py
+++ b/pandas/core/indexes/accessors.py
@@ -85,9 +85,7 @@ class Properties(PandasDelegate, PandasObject, NoNewAttributesMixin):
             f"cannot convert an object of type {type(data)} to a datetimelike index"
         )
 
-    # error: Signature of "_delegate_property_get" incompatible with supertype
-    # "PandasDelegate"
-    def _delegate_property_get(self, name: str):  # type: ignore[override]
+    def _delegate_property_get(self, name: str):
         from pandas import Series
 
         values = self._get_values()
@@ -175,7 +173,7 @@ class ArrowTemporalProperties(PandasDelegate, PandasObject, NoNewAttributesMixin
         self._orig = orig
         self._freeze()
 
-    def _delegate_property_get(self, name: str):  # type: ignore[override]
+    def _delegate_property_get(self, name: str):
         if not hasattr(self._parent.array, f"_dt_{name}"):
             raise NotImplementedError(
                 f"dt.{name} is not supported for {self._parent.dtype}"
diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py
index 9a5fae445d..17ed50dad2 100644
--- a/pandas/core/indexes/base.py
+++ b/pandas/core/indexes/base.py
@@ -71,6 +71,7 @@ from pandas.errors import (
 from pandas.util._decorators import (
     Appender,
     cache_readonly,
+    deprecate_nonkeyword_arguments,
     doc,
 )
 from pandas.util._exceptions import (
@@ -1896,7 +1897,18 @@ class Index(IndexOpsMixin, PandasObject):
             return idx
         return None
 
-    def rename(self, name, inplace: bool = False):
+    @overload
+    def rename(self, name, *, inplace: Literal[False] = ...) -> Self:
+        ...
+
+    @overload
+    def rename(self, name, *, inplace: Literal[True]) -> None:
+        ...
+
+    @deprecate_nonkeyword_arguments(
+        version="3.0", allowed_args=["self", "name"], name="rename"
+    )
+    def rename(self, name, inplace: bool = False) -> Self | None:
         """
         Alter Index or MultiIndex name.
 
@@ -5792,13 +5804,49 @@ class Index(IndexOpsMixin, PandasObject):
 
         return result
 
+    @overload
+    def sort_values(
+        self,
+        *,
+        return_indexer: Literal[False] = ...,
+        ascending: bool = ...,
+        na_position: NaPosition = ...,
+        key: Callable | None = ...,
+    ) -> Self:
+        ...
+
+    @overload
+    def sort_values(
+        self,
+        *,
+        return_indexer: Literal[True],
+        ascending: bool = ...,
+        na_position: NaPosition = ...,
+        key: Callable | None = ...,
+    ) -> tuple[Self, np.ndarray]:
+        ...
+
+    @overload
+    def sort_values(
+        self,
+        *,
+        return_indexer: bool = ...,
+        ascending: bool = ...,
+        na_position: NaPosition = ...,
+        key: Callable | None = ...,
+    ) -> Self | tuple[Self, np.ndarray]:
+        ...
+
+    @deprecate_nonkeyword_arguments(
+        version="3.0", allowed_args=["self"], name="sort_values"
+    )
     def sort_values(
         self,
         return_indexer: bool = False,
         ascending: bool = True,
         na_position: NaPosition = "last",
         key: Callable | None = None,
-    ):
+    ) -> Self | tuple[Self, np.ndarray]:
         """
         Return a sorted copy of the index.
 
diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py
index 46343b84af..2a4e027e2b 100644
--- a/pandas/core/indexes/multi.py
+++ b/pandas/core/indexes/multi.py
@@ -4054,14 +4054,18 @@ def sparsify_labels(label_list, start: int = 0, sentinel: object = ""):
         for i, (p, t) in enumerate(zip(prev, cur)):
             if i == k - 1:
                 sparse_cur.append(t)
-                result.append(sparse_cur)
+                # error: Argument 1 to "append" of "list" has incompatible
+                # type "list[Any]"; expected "tuple[Any, ...]"
+                result.append(sparse_cur)  # type: ignore[arg-type]
                 break
 
             if p == t:
                 sparse_cur.append(sentinel)
             else:
                 sparse_cur.extend(cur[i:])
-                result.append(sparse_cur)
+                # error: Argument 1 to "append" of "list" has incompatible
+                # type "list[Any]"; expected "tuple[Any, ...]"
+                result.append(sparse_cur)  # type: ignore[arg-type]
                 break
 
         prev = cur
diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py
index 30b11aea63..62afcf8bad 100644
--- a/pandas/core/indexes/range.py
+++ b/pandas/core/indexes/range.py
@@ -11,7 +11,9 @@ from typing import (
     TYPE_CHECKING,
     Any,
     Callable,
+    Literal,
     cast,
+    overload,
 )
 
 import numpy as np
@@ -25,6 +27,7 @@ from pandas._libs.lib import no_default
 from pandas.compat.numpy import function as nv
 from pandas.util._decorators import (
     cache_readonly,
+    deprecate_nonkeyword_arguments,
     doc,
 )
 
@@ -555,13 +558,50 @@ class RangeIndex(Index):
             return self._range == other._range
         return super().equals(other)
 
+    # error: Signature of "sort_values" incompatible with supertype "Index"
+    @overload  # type: ignore[override]
+    def sort_values(
+        self,
+        *,
+        return_indexer: Literal[False] = ...,
+        ascending: bool = ...,
+        na_position: NaPosition = ...,
+        key: Callable | None = ...,
+    ) -> Self:
+        ...
+
+    @overload
+    def sort_values(
+        self,
+        *,
+        return_indexer: Literal[True],
+        ascending: bool = ...,
+        na_position: NaPosition = ...,
+        key: Callable | None = ...,
+    ) -> tuple[Self, np.ndarray | RangeIndex]:
+        ...
+
+    @overload
+    def sort_values(
+        self,
+        *,
+        return_indexer: bool = ...,
+        ascending: bool = ...,
+        na_position: NaPosition = ...,
+        key: Callable | None = ...,
+    ) -> Self | tuple[Self, np.ndarray | RangeIndex]:
+        ...
+
+    @deprecate_nonkeyword_arguments(
+        version="3.0", allowed_args=["self"], name="sort_values"
+    )
     def sort_values(
         self,
         return_indexer: bool = False,
         ascending: bool = True,
         na_position: NaPosition = "last",
         key: Callable | None = None,
-    ):
+    ) -> Self | tuple[Self, np.ndarray | RangeIndex]:
         if key is not None:
             return super().sort_values(
                 return_indexer=return_indexer,
diff --git a/pandas/core/internals/base.py b/pandas/core/internals/base.py
index 33f5b9feb1..ae91f16720 100644
--- a/pandas/core/internals/base.py
+++ b/pandas/core/internals/base.py
@@ -146,7 +146,7 @@ class DataManager(PandasObject):
         """
         Implementation for DataFrame.equals
         """
-        if not isinstance(other, DataManager):
+        if not isinstance(other, type(self)):
             return False
 
         self_axes, other_axes = self.axes, other.axes
diff --git a/pandas/core/methods/describe.py b/pandas/core/methods/describe.py
index dcdf0067d4..c620bb9d17 100644
--- a/pandas/core/methods/describe.py
+++ b/pandas/core/methods/describe.py
@@ -146,6 +146,8 @@ class DataFrameDescriber(NDFrameDescriberAbstract):
         A black list of data types to omit from the result.
     """
 
+    obj: DataFrame
+
     def __init__(
         self,
         obj: DataFrame,
@@ -196,7 +198,7 @@ class DataFrameDescriber(NDFrameDescriberAbstract):
                 include=self.include,
                 exclude=self.exclude,
             )
-        return data  # pyright: ignore[reportGeneralTypeIssues]
+        return data
 
 
 def reorder_columns(ldesc: Sequence[Series]) -> list[Hashable]:
diff --git a/pandas/core/nanops.py b/pandas/core/nanops.py
index 20dc3f20bb..6cb825e9b7 100644
--- a/pandas/core/nanops.py
+++ b/pandas/core/nanops.py
@@ -1145,9 +1145,10 @@ def nanargmax(
     array([2, 2, 1, 1])
     """
     values, mask = _get_values(values, True, fill_value_typ="-inf", mask=mask)
-    # error: Need type annotation for 'result'
-    result = values.argmax(axis)  # type: ignore[var-annotated]
-    result = _maybe_arg_null_out(result, axis, mask, skipna)
+    result = values.argmax(axis)
+    # error: Argument 1 to "_maybe_arg_null_out" has incompatible type "Any |
+    # signedinteger[Any]"; expected "ndarray[Any, Any]"
+    result = _maybe_arg_null_out(result, axis, mask, skipna)  # type: ignore[arg-type]
     return result
 
 
@@ -1190,9 +1191,10 @@ def nanargmin(
     array([0, 0, 1, 1])
     """
     values, mask = _get_values(values, True, fill_value_typ="+inf", mask=mask)
-    # error: Need type annotation for 'result'
-    result = values.argmin(axis)  # type: ignore[var-annotated]
-    result = _maybe_arg_null_out(result, axis, mask, skipna)
+    result = values.argmin(axis)
+    # error: Argument 1 to "_maybe_arg_null_out" has incompatible type "Any |
+    # signedinteger[Any]"; expected "ndarray[Any, Any]"
+    result = _maybe_arg_null_out(result, axis, mask, skipna)  # type: ignore[arg-type]
     return result
 
 
diff --git a/pandas/core/reshape/concat.py b/pandas/core/reshape/concat.py
index d46348fff7..aacea92611 100644
--- a/pandas/core/reshape/concat.py
+++ b/pandas/core/reshape/concat.py
@@ -865,12 +865,14 @@ def _make_concat_multiindex(indexes, keys, levels=None, names=None) -> MultiInde
     # do something a bit more speedy
 
     for hlevel, level in zip(zipped, levels):
-        hlevel = ensure_index(hlevel)
-        mapped = level.get_indexer(hlevel)
+        hlevel_index = ensure_index(hlevel)
+        mapped = level.get_indexer(hlevel_index)
 
         mask = mapped == -1
         if mask.any():
-            raise ValueError(f"Values not found in passed level: {hlevel[mask]!s}")
+            raise ValueError(
+                f"Values not found in passed level: {hlevel_index[mask]!s}"
+            )
 
         new_codes.append(np.repeat(mapped, n))
 
diff --git a/pandas/core/reshape/util.py b/pandas/core/reshape/util.py
index bcd51e095a..476e3922b6 100644
--- a/pandas/core/reshape/util.py
+++ b/pandas/core/reshape/util.py
@@ -63,7 +63,7 @@ def cartesian_product(X) -> list[np.ndarray]:
     return [
         tile_compat(
             np.repeat(x, b[i]),
-            np.prod(a[i]),  # pyright: ignore[reportGeneralTypeIssues]
+            np.prod(a[i]),
         )
         for i, x in enumerate(X)
     ]
diff --git a/pandas/io/excel/_calamine.py b/pandas/io/excel/_calamine.py
index 1b30d0f759..4f65acf1aa 100644
--- a/pandas/io/excel/_calamine.py
+++ b/pandas/io/excel/_calamine.py
@@ -10,10 +10,8 @@ from typing import (
     TYPE_CHECKING,
     Any,
     Union,
-    cast,
 )
 
-from pandas._typing import Scalar
 from pandas.compat._optional import import_optional_dependency
 from pandas.util._decorators import doc
 
@@ -30,11 +28,13 @@ if TYPE_CHECKING:
 
     from pandas._typing import (
         FilePath,
+        NaTType,
         ReadBuffer,
+        Scalar,
         StorageOptions,
     )
 
-_CellValueT = Union[int, float, str, bool, time, date, datetime, timedelta]
+_CellValue = Union[int, float, str, bool, time, date, datetime, timedelta]
 
 
 class CalamineReader(BaseExcelReader["CalamineWorkbook"]):
@@ -98,8 +98,8 @@ class CalamineReader(BaseExcelReader["CalamineWorkbook"]):
 
     def get_sheet_data(
         self, sheet: CalamineSheet, file_rows_needed: int | None = None
-    ) -> list[list[Scalar]]:
-        def _convert_cell(value: _CellValueT) -> Scalar:
+    ) -> list[list[Scalar | NaTType | time]]:
+        def _convert_cell(value: _CellValue) -> Scalar | NaTType | time:
             if isinstance(value, float):
                 val = int(value)
                 if val == value:
@@ -111,16 +111,13 @@ class CalamineReader(BaseExcelReader["CalamineWorkbook"]):
             elif isinstance(value, timedelta):
                 return pd.Timedelta(value)
             elif isinstance(value, time):
-                # cast needed here because Scalar doesn't include datetime.time
-                return cast(Scalar, value)
+                return value
 
             return value
 
-        rows: list[list[_CellValueT]] = sheet.to_python(
+        rows: list[list[_CellValue]] = sheet.to_python(
             skip_empty_area=False, nrows=file_rows_needed
         )
-        data: list[list[Scalar]] = [
-            [_convert_cell(cell) for cell in row] for row in rows
-        ]
+        data = [[_convert_cell(cell) for cell in row] for row in rows]
 
         return data
diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py
index 445d60be1d..00c7526edf 100644
--- a/pandas/io/formats/format.py
+++ b/pandas/io/formats/format.py
@@ -796,13 +796,13 @@ class DataFrameFormatter:
                     return " " + y
                 return y
 
-            str_columns = list(
+            str_columns_tuple = list(
                 zip(*([space_format(x, y) for y in x] for x in fmt_columns))
             )
-            if self.sparsify and len(str_columns):
-                str_columns = sparsify_labels(str_columns)
+            if self.sparsify and len(str_columns_tuple):
+                str_columns_tuple = sparsify_labels(str_columns_tuple)
 
-            str_columns = [list(x) for x in zip(*str_columns)]
+            str_columns = [list(x) for x in zip(*str_columns_tuple)]
         else:
             fmt_columns = columns._format_flat(include_name=False)
             dtypes = self.frame.dtypes
diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py
index be269dffad..ed66e46b30 100644
--- a/pandas/io/json/_json.py
+++ b/pandas/io/json/_json.py
@@ -757,6 +757,19 @@ def read_json(
 {{"index":"row 2","col 1":"c","col 2":"d"}}]\
 }}\
 '
+
+    The following example uses ``dtype_backend="numpy_nullable"``
+
+    >>> data = '''{{"index": {{"0": 0, "1": 1}},
+    ...        "a": {{"0": 1, "1": null}},
+    ...        "b": {{"0": 2.5, "1": 4.5}},
+    ...        "c": {{"0": true, "1": false}},
+    ...        "d": {{"0": "a", "1": "b"}},
+    ...        "e": {{"0": 1577.2, "1": 1577.1}}}}'''
+    >>> pd.read_json(StringIO(data), dtype_backend="numpy_nullable")
+       index     a    b      c  d       e
+    0      0     1  2.5   True  a  1577.2
+    1      1  <NA>  4.5  False  b  1577.1
     """
     if orient == "table" and dtype:
         raise ValueError("cannot pass both dtype and orient='table'")
diff --git a/pandas/io/parquet.py b/pandas/io/parquet.py
index 295eb3cbdd..9570d6f8b2 100644
--- a/pandas/io/parquet.py
+++ b/pandas/io/parquet.py
@@ -207,9 +207,10 @@ class PyArrowImpl(BaseImpl):
             and hasattr(path_or_handle, "name")
             and isinstance(path_or_handle.name, (str, bytes))
         ):
-            path_or_handle = path_or_handle.name
-            if isinstance(path_or_handle, bytes):
-                path_or_handle = path_or_handle.decode()
+            if isinstance(path_or_handle.name, bytes):
+                path_or_handle = path_or_handle.name.decode()
+            else:
+                path_or_handle = path_or_handle.name
 
         try:
             if partition_cols is not None:
diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py
index 1355bda902..cb5598a98d 100644
--- a/pandas/plotting/_core.py
+++ b/pandas/plotting/_core.py
@@ -1884,7 +1884,7 @@ def _load_backend(backend: str) -> types.ModuleType:
     # entry_points lost dict API ~ PY 3.10
     # https://github.com/python/importlib_metadata/issues/298
     if hasattr(eps, "select"):
-        entry = eps.select(group=key)  # pyright: ignore[reportGeneralTypeIssues]
+        entry = eps.select(group=key)
     else:
         # Argument 2 to "get" of "dict" has incompatible type "Tuple[]";
         # expected "EntryPoints"  [arg-type]
diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py
index 2a1503b84a..479a5e19dc 100644
--- a/pandas/plotting/_matplotlib/core.py
+++ b/pandas/plotting/_matplotlib/core.py
@@ -1178,7 +1178,7 @@ class MPLPlot(ABC):
 
         elif is_number(err):
             err = np.tile(
-                [err],  # pyright: ignore[reportGeneralTypeIssues]
+                [err],
                 (nseries, len(data)),
             )
 
@@ -1186,7 +1186,7 @@ class MPLPlot(ABC):
             msg = f"No valid {label} detected"
             raise ValueError(msg)
 
-        return err, data  # pyright: ignore[reportGeneralTypeIssues]
+        return err, data
 
     @final
     def _get_errorbars(
@@ -1365,7 +1365,7 @@ class ScatterPlot(PlanePlot):
                 # error: Argument 2 to "_append_legend_handles_labels" of
                 # "MPLPlot" has incompatible type "Hashable"; expected "str"
                 scatter,
-                label,  # type: ignore[arg-type]  # pyright: ignore[reportGeneralTypeIssues]
+                label,  # type: ignore[arg-type]
             )
 
         errors_x = self._get_errorbars(label=x, index=0, yerr=False)
@@ -1531,7 +1531,7 @@ class LinePlot(MPLPlot):
                 i,
                 # error: Argument 4 to "_apply_style_colors" of "MPLPlot" has
                 # incompatible type "Hashable"; expected "str"
-                label,  # type: ignore[arg-type] # pyright: ignore[reportGeneralTypeIssues]
+                label,  # type: ignore[arg-type]
             )
 
             errors = self._get_errorbars(label=label, index=i)
@@ -1544,7 +1544,7 @@ class LinePlot(MPLPlot):
             newlines = plotf(
                 ax,
                 x,
-                y,  # pyright: ignore[reportGeneralTypeIssues]
+                y,
                 style=style,
                 column_num=i,
                 stacking_id=stacking_id,
diff --git a/pyproject.toml b/pyproject.toml
index f6ba25f448..1831f5afd3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -754,6 +754,7 @@ reportUntypedClassDecorator = true
 reportUntypedFunctionDecorator = true
 reportUntypedNamedTuple = true
 reportUnusedImport = true
+disableBytesTypePromotions = true
 # disable subset of "basic"
 reportGeneralTypeIssues = false
 reportMissingModuleSource = false
diff --git a/pyright_reportGeneralTypeIssues.json b/pyright_reportGeneralTypeIssues.json
index e155b34053..a38343d619 100644
--- a/pyright_reportGeneralTypeIssues.json
+++ b/pyright_reportGeneralTypeIssues.json
@@ -2,6 +2,8 @@
     "typeCheckingMode": "off",
     "reportGeneralTypeIssues": true,
     "useLibraryCodeForTypes": false,
+    "analyzeUnannotatedFunctions": false,
+    "disableBytesTypePromotions": true,
     "include":
       [
         "pandas",
@@ -16,10 +18,10 @@
 
         "pandas/_testing/__init__.py",
         "pandas/_testing/_io.py",
+        "pandas/compat/pickle_compat.py",
         "pandas/core/_numba/extensions.py",
         "pandas/core/_numba/kernels/sum_.py",
         "pandas/core/_numba/kernels/var_.py",
-        "pandas/compat/pickle_compat.py",
         "pandas/core/algorithms.py",
         "pandas/core/apply.py",
         "pandas/core/array_algos/take.py",
@@ -39,6 +41,7 @@
         "pandas/core/arrays/string_arrow.py",
         "pandas/core/arrays/timedeltas.py",
         "pandas/core/computation/align.py",
+        "pandas/core/computation/ops.py",
         "pandas/core/construction.py",
         "pandas/core/dtypes/cast.py",
         "pandas/core/dtypes/common.py",
@@ -89,6 +92,7 @@
         "pandas/io/formats/info.py",
         "pandas/io/formats/printing.py",
         "pandas/io/formats/style.py",
+        "pandas/io/formats/style_render.py",
         "pandas/io/json/_json.py",
         "pandas/io/json/_normalize.py",
         "pandas/io/parsers/arrow_parser_wrapper.py",
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 50a81fbff1..76f4de2d8f 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -53,7 +53,7 @@ moto
 flask
 asv>=0.6.1
 flake8==6.1.0
-mypy==1.4.1
+mypy==1.7.1
 tokenize-rt
 pre-commit>=3.6.0
 gitpython


Test patch:
diff --git a/pandas/_testing/_hypothesis.py b/pandas/_testing/_hypothesis.py
index 5256a303de..084ca9c306 100644
--- a/pandas/_testing/_hypothesis.py
+++ b/pandas/_testing/_hypothesis.py
@@ -54,8 +54,12 @@ else:
     DATETIME_NO_TZ = st.datetimes()
 
 DATETIME_JAN_1_1900_OPTIONAL_TZ = st.datetimes(
-    min_value=pd.Timestamp(1900, 1, 1).to_pydatetime(),
-    max_value=pd.Timestamp(1900, 1, 1).to_pydatetime(),
+    min_value=pd.Timestamp(
+        1900, 1, 1
+    ).to_pydatetime(),  # pyright: ignore[reportGeneralTypeIssues]
+    max_value=pd.Timestamp(
+        1900, 1, 1
+    ).to_pydatetime(),  # pyright: ignore[reportGeneralTypeIssues]
     timezones=st.one_of(st.none(), dateutil_timezones(), pytz_timezones()),
 )
 
diff --git a/pandas/conftest.py b/pandas/conftest.py
index 7bfd1b35f5..53173fe93b 100644
--- a/pandas/conftest.py
+++ b/pandas/conftest.py
@@ -1353,7 +1353,7 @@ def fixed_now_ts() -> Timestamp:
     """
     Fixture emits fixed Timestamp.now()
     """
-    return Timestamp(
+    return Timestamp(  # pyright: ignore[reportGeneralTypeIssues]
         year=2021, month=1, day=1, hour=12, minute=4, second=13, microsecond=22
     )
 
diff --git a/pandas/tests/frame/methods/test_fillna.py b/pandas/tests/frame/methods/test_fillna.py
index 1403a45a5c..6757669351 100644
--- a/pandas/tests/frame/methods/test_fillna.py
+++ b/pandas/tests/frame/methods/test_fillna.py
@@ -857,3 +857,100 @@ def test_pad_backfill_deprecated(func):
     df = DataFrame({"a": [1, 2, 3]})
     with tm.assert_produces_warning(FutureWarning):
         getattr(df, func)()
+
+
+@pytest.mark.parametrize(
+    "data, expected_data, method, kwargs",
+    (
+        pytest.param(
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, 3.0, 3.0, 3.0, 7.0, np.nan, np.nan],
+            "ffill",
+            {"limit_area": "inside"},
+            marks=pytest.mark.xfail(
+                reason="GH#41813 - limit_area applied to the wrong axis"
+            ),
+        ),
+        pytest.param(
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, 3.0, np.nan, np.nan, 7.0, np.nan, np.nan],
+            "ffill",
+            {"limit_area": "inside", "limit": 1},
+            marks=pytest.mark.xfail(
+                reason="GH#41813 - limit_area applied to the wrong axis"
+            ),
+        ),
+        pytest.param(
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, 7.0],
+            "ffill",
+            {"limit_area": "outside"},
+            marks=pytest.mark.xfail(
+                reason="GH#41813 - limit_area applied to the wrong axis"
+            ),
+        ),
+        pytest.param(
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, np.nan],
+            "ffill",
+            {"limit_area": "outside", "limit": 1},
+            marks=pytest.mark.xfail(
+                reason="GH#41813 - limit_area applied to the wrong axis"
+            ),
+        ),
+        (
+            [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
+            [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
+            "ffill",
+            {"limit_area": "outside", "limit": 1},
+        ),
+        (
+            range(5),
+            range(5),
+            "ffill",
+            {"limit_area": "outside", "limit": 1},
+        ),
+        pytest.param(
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, 7.0, 7.0, 7.0, 7.0, np.nan, np.nan],
+            "bfill",
+            {"limit_area": "inside"},
+            marks=pytest.mark.xfail(
+                reason="GH#41813 - limit_area applied to the wrong axis"
+            ),
+        ),
+        pytest.param(
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, np.nan, np.nan, 7.0, 7.0, np.nan, np.nan],
+            "bfill",
+            {"limit_area": "inside", "limit": 1},
+            marks=pytest.mark.xfail(
+                reason="GH#41813 - limit_area applied to the wrong axis"
+            ),
+        ),
+        pytest.param(
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [3.0, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan],
+            "bfill",
+            {"limit_area": "outside"},
+            marks=pytest.mark.xfail(
+                reason="GH#41813 - limit_area applied to the wrong axis"
+            ),
+        ),
+        pytest.param(
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan],
+            "bfill",
+            {"limit_area": "outside", "limit": 1},
+            marks=pytest.mark.xfail(
+                reason="GH#41813 - limit_area applied to the wrong axis"
+            ),
+        ),
+    ),
+)
+def test_ffill_bfill_limit_area(data, expected_data, method, kwargs):
+    # GH#56492
+    df = DataFrame(data)
+    expected = DataFrame(expected_data)
+    result = getattr(df, method)(**kwargs)
+    tm.assert_frame_equal(result, expected)
diff --git a/pandas/tests/groupby/test_api.py b/pandas/tests/groupby/test_api.py
index 3066825352..5c5982954d 100644
--- a/pandas/tests/groupby/test_api.py
+++ b/pandas/tests/groupby/test_api.py
@@ -183,7 +183,7 @@ def test_frame_consistency(groupby_func):
     elif groupby_func in ("median", "prod", "sem"):
         exclude_expected = {"axis", "kwargs", "skipna"}
     elif groupby_func in ("backfill", "bfill", "ffill", "pad"):
-        exclude_expected = {"downcast", "inplace", "axis"}
+        exclude_expected = {"downcast", "inplace", "axis", "limit_area"}
     elif groupby_func in ("cummax", "cummin"):
         exclude_expected = {"skipna", "args"}
         exclude_result = {"numeric_only"}
@@ -240,7 +240,7 @@ def test_series_consistency(request, groupby_func):
     elif groupby_func in ("median", "prod", "sem"):
         exclude_expected = {"axis", "kwargs", "skipna"}
     elif groupby_func in ("backfill", "bfill", "ffill", "pad"):
-        exclude_expected = {"downcast", "inplace", "axis"}
+        exclude_expected = {"downcast", "inplace", "axis", "limit_area"}
     elif groupby_func in ("cummax", "cummin"):
         exclude_expected = {"skipna", "args"}
         exclude_result = {"numeric_only"}
diff --git a/pandas/tests/series/methods/test_fillna.py b/pandas/tests/series/methods/test_fillna.py
index acc5805578..293259661c 100644
--- a/pandas/tests/series/methods/test_fillna.py
+++ b/pandas/tests/series/methods/test_fillna.py
@@ -1080,3 +1080,76 @@ class TestFillnaPad:
         ser = Series([1, 2, 3])
         with tm.assert_produces_warning(FutureWarning):
             getattr(ser, func)()
+
+
+@pytest.mark.parametrize(
+    "data, expected_data, method, kwargs",
+    (
+        (
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, 3.0, 3.0, 3.0, 7.0, np.nan, np.nan],
+            "ffill",
+            {"limit_area": "inside"},
+        ),
+        (
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, 3.0, np.nan, np.nan, 7.0, np.nan, np.nan],
+            "ffill",
+            {"limit_area": "inside", "limit": 1},
+        ),
+        (
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, 7.0],
+            "ffill",
+            {"limit_area": "outside"},
+        ),
+        (
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 7.0, 7.0, np.nan],
+            "ffill",
+            {"limit_area": "outside", "limit": 1},
+        ),
+        (
+            [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
+            [np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan],
+            "ffill",
+            {"limit_area": "outside", "limit": 1},
+        ),
+        (
+            range(5),
+            range(5),
+            "ffill",
+            {"limit_area": "outside", "limit": 1},
+        ),
+        (
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, 7.0, 7.0, 7.0, 7.0, np.nan, np.nan],
+            "bfill",
+            {"limit_area": "inside"},
+        ),
+        (
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, np.nan, 3.0, np.nan, np.nan, 7.0, 7.0, np.nan, np.nan],
+            "bfill",
+            {"limit_area": "inside", "limit": 1},
+        ),
+        (
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [3.0, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan],
+            "bfill",
+            {"limit_area": "outside"},
+        ),
+        (
+            [np.nan, np.nan, 3, np.nan, np.nan, np.nan, 7, np.nan, np.nan],
+            [np.nan, 3.0, 3.0, np.nan, np.nan, np.nan, 7.0, np.nan, np.nan],
+            "bfill",
+            {"limit_area": "outside", "limit": 1},
+        ),
+    ),
+)
+def test_ffill_bfill_limit_area(data, expected_data, method, kwargs):
+    # GH#56492
+    s = Series(data)
+    expected = Series(expected_data)
+    result = getattr(s, method)(**kwargs)
+    tm.assert_series_equal(result, expected)