Pin numpy to get ci back to green
- [x] xref #45182
- [ ] tests added / passed
- [ ] Ensure all linting tests pass, see [here](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit) for how to run them
- [ ] whatsnew entry

Let's see if this gets us back to green
hmm something odd happeneing in the release then.

cc @seberg @charris I think

<details>
```
    @td.skip_if_no("numba")
    def test_alignment_deprecation_many_inputs():
        # https://github.com/pandas-dev/pandas/issues/39184
        # test that the deprecation also works with > 2 inputs -> using a numba
        # written ufunc for this because numpy itself doesn't have such ufuncs
        from numba import (
            float64,
            vectorize,
        )
    
        @vectorize([float64(float64, float64, float64)])
        def my_ufunc(x, y, z):
            return x + y + z
    
        df1 = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]})
        df2 = pd.DataFrame({"b": [1, 2, 3], "c": [4, 5, 6]})
        df3 = pd.DataFrame({"a": [1, 2, 3], "c": [4, 5, 6]})
    
        with tm.assert_produces_warning(FutureWarning):
>           result = my_ufunc(df1, df2, df3)

pandas/tests/frame/test_ufunc.py:273: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
pandas/core/generic.py:2078: in __array_ufunc__
    return arraylike.array_ufunc(self, ufunc, method, *inputs, **kwargs)
pandas/core/arraylike.py:258: in array_ufunc
    result = _maybe_fallback(ufunc, method, *inputs, **kwargs)
pandas/core/arraylike.py:236: in _maybe_fallback
    return getattr(ufunc, method)(*new_inputs, **kwargs)
pandas/core/generic.py:2078: in __array_ufunc__
    return arraylike.array_ufunc(self, ufunc, method, *inputs, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self =    a  b
0  1  4
1  2  5
2  3  6, ufunc = <ufunc 'my_ufunc'>
method = '__call__'
inputs = (array([[1, 4],
       [2, 5],
       [3, 6]]), array([[1, 4],
       [2, 5],
       [3, 6]]), array([[1, 4],
       [2, 5],
       [3, 6]]))
kwargs = {}, cls = <class 'pandas.core.frame.DataFrame'>
result = NotImplemented
no_defer = (<method '__array_ufunc__' of 'numpy.ndarray' objects>, <function NDFrame.__array_ufunc__ at 0x7fed5e53dee0>)
item = array([[1, 4],
       [2, 5],
       [3, 6]]), higher_priority = False
has_array_ufunc = False
types = (<class 'pandas.core.frame.DataFrame'>, <class 'numpy.ndarray'>, <class 'numpy.ndarray'>)

    def array_ufunc(self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any):
        """
        Compatibility with numpy ufuncs.
    
        See also
        --------
        numpy.org/doc/stable/reference/arrays.classes.html#numpy.class.__array_ufunc__
        """
        from pandas.core.generic import NDFrame
        from pandas.core.internals import BlockManager
    
        cls = type(self)
    
        kwargs = _standardize_out_kwarg(**kwargs)
    
        # for backwards compatibility check and potentially fallback for non-aligned frames
        result = _maybe_fallback(ufunc, method, *inputs, **kwargs)
        if result is not NotImplemented:
            return result
    
        # for binary ops, use our custom dunder methods
        result = maybe_dispatch_ufunc_to_dunder_op(self, ufunc, method, *inputs, **kwargs)
        if result is not NotImplemented:
            return result
    
        # Determine if we should defer.
    
        # error: "Type[ndarray]" has no attribute "__array_ufunc__"
        no_defer = (
            np.ndarray.__array_ufunc__,  # type: ignore[attr-defined]
            cls.__array_ufunc__,
        )
    
        for item in inputs:
            higher_priority = (
                hasattr(item, "__array_priority__")
                and item.__array_priority__ > self.__array_priority__
            )
            has_array_ufunc = (
                hasattr(item, "__array_ufunc__")
                and type(item).__array_ufunc__ not in no_defer
                and not isinstance(item, self._HANDLED_TYPES)
            )
            if higher_priority or has_array_ufunc:
                return NotImplemented
    
        # align all the inputs.
        types = tuple(type(x) for x in inputs)
        alignable = [x for x, t in zip(inputs, types) if issubclass(t, NDFrame)]
    
        if len(alignable) > 1:
            # This triggers alignment.
            # At the moment, there aren't any ufuncs with more than two inputs
            # so this ends up just being x1.index | x2.index, but we write
            # it to handle *args.
    
            if len(set(types)) > 1:
                # We currently don't handle ufunc(DataFrame, Series)
                # well. Previously this raised an internal ValueError. We might
                # support it someday, so raise a NotImplementedError.
                raise NotImplementedError(
                    "Cannot apply ufunc {} to mixed DataFrame and Series "
                    "inputs.".format(ufunc)
                )
            axes = self.axes
            for obj in alignable[1:]:
                # this relies on the fact that we aren't handling mixed
                # series / frame ufuncs.
                for i, (ax1, ax2) in enumerate(zip(axes, obj.axes)):
                    axes[i] = ax1.union(ax2)
    
            reconstruct_axes = dict(zip(self._AXIS_ORDERS, axes))
            inputs = tuple(
                x.reindex(**reconstruct_axes) if issubclass(t, NDFrame) else x
                for x, t in zip(inputs, types)
            )
        else:
            reconstruct_axes = dict(zip(self._AXIS_ORDERS, self.axes))
    
        if self.ndim == 1:
            names = [getattr(x, "name") for x in inputs if hasattr(x, "name")]
            name = names[0] if len(set(names)) == 1 else None
            reconstruct_kwargs = {"name": name}
        else:
            reconstruct_kwargs = {}
    
        def reconstruct(result):
            if ufunc.nout > 1:
                # np.modf, np.frexp, np.divmod
                return tuple(_reconstruct(x) for x in result)
    
            return _reconstruct(result)
    
        def _reconstruct(result):
            if lib.is_scalar(result):
                return result
    
            if result.ndim != self.ndim:
                if method == "outer":
                    if self.ndim == 2:
                        # we already deprecated for Series
                        msg = (
                            "outer method for ufunc {} is not implemented on "
                            "pandas objects. Returning an ndarray, but in the "
                            "future this will raise a 'NotImplementedError'. "
                            "Consider explicitly converting the DataFrame "
                            "to an array with '.to_numpy()' first."
                        )
                        warnings.warn(
                            msg.format(ufunc), FutureWarning, stacklevel=find_stack_level()
                        )
                        return result
                    raise NotImplementedError
                return result
            if isinstance(result, BlockManager):
                # we went through BlockManager.apply e.g. np.sqrt
                result = self._constructor(result, **reconstruct_kwargs, copy=False)
            else:
                # we converted an array, lost our axes
                result = self._constructor(
                    result, **reconstruct_axes, **reconstruct_kwargs, copy=False
                )
            # TODO: When we support multiple values in __finalize__, this
            # should pass alignable to `__finalize__` instead of self.
            # Then `np.add(a, b)` would consider attrs from both a and b
            # when a and b are NDFrames.
            if len(alignable) == 1:
                result = result.__finalize__(self)
            return result
    
        if "out" in kwargs:
            # e.g. test_multiindex_get_loc
            result = dispatch_ufunc_with_out(self, ufunc, method, *inputs, **kwargs)
            return reconstruct(result)
    
        if method == "reduce":
            # e.g. test.series.test_ufunc.test_reduce
            result = dispatch_reduction_ufunc(self, ufunc, method, *inputs, **kwargs)
            if result is not NotImplemented:
                return result
    
        # We still get here with kwargs `axis` for e.g. np.maximum.accumulate
        #  and `dtype` and `keepdims` for np.ptp
    
        if self.ndim > 1 and (len(inputs) > 1 or ufunc.nout > 1):
            # Just give up on preserving types in the complex case.
            # In theory we could preserve them for them.
            # * nout>1 is doable if BlockManager.apply took nout and
            #   returned a Tuple[BlockManager].
            # * len(inputs) > 1 is doable when we know that we have
            #   aligned blocks / dtypes.
    
            # e.g. my_ufunc, modf, logaddexp, heaviside, subtract, add
            inputs = tuple(np.asarray(x) for x in inputs)
            # Note: we can't use default_array_ufunc here bc reindexing means
            #  that `self` may not be among `inputs`
>           result = getattr(ufunc, method)(*inputs, **kwargs)
E           numpy.core._exceptions._UFuncNoLoopError: ufunc 'my_ufunc' did not contain a loop with signature matching types (<class 'numpy.dtype[int64]'>, <class 'numpy.dtype[int64]'>, <class 'numpy.dtype[int64]'>) -> None
```
</details>

is the only real failure apart from the typing issues xfailing for new numpy and pinning only for typing checks let's re-run this This looks good now, the lagging build has numpy 1.18.5 do we have an issue to revert the numpy pin? can u post a failing test here (not the one u x failed but) I have linked the issue above, #45182 
I posted the x-failed test and the typing issues there sure but do we actually need the numpy pin now that u x failed that particular test? In the global environment yml yes, because this one is used for the typing checks.  ok got it thxs