TYP: interval.pyi
State: closed

~~Currently rebased on top of #44339.~~

This is the second last part of @erictraut's #43744 (offsets.pyi is still missing).

Labels:
name: Typing
description: type annotations, mypy/pyright type checking

Comments:
Hello @twoertwein! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:

















* In the file [`pandas/io/excel/_odfreader.py`](https://github.com/pandas-dev/pandas/blob/029a6a869c9b57b846bdffac5065d4242cb543e4/pandas/io/excel/_odfreader.py):

> [Line 89:89](https://github.com/pandas-dev/pandas/blob/029a6a869c9b57b846bdffac5065d4242cb543e4/pandas/io/excel/_odfreader.py#L89): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (89 > 88 characters)







##### Comment last updated at 2022-02-21 04:04:50 UTC
I liket he _ScalarT better for a name of the typevar (as compared to _S in timestamps.pyi)
cc @simonjayhawkins 
cc @jbrockmendel 
I just realized that typing interval.pyi is much messier than I expected:

1. A TypeVar cannot be defined by a generic type, but Interval is now a generic type which depends on Scalar (includes Interval) and uses it as a TypeVar
2. IntervalMixin should also be a Generic (the two float return types should be _ScalarT) but IntervalTree inherits also from IntervalMixin which means that the generic type of IntervalMixin is not only Scalar but also np.ndarray
3. `__sub__` and `length` should reflect the datetime/timedelta mechanics (datetime - datetime = timedelta)
4. and `__add__` should probably not allow datetime + datetime (but datetime + timedelta)

I think the only way to address the above is to use define a Protocol (needs to support +, -, <, ...) and then use this protocol as a bound for a TypeVar which is then used by IntervalMixin&co. The datetime/timedelta mechanics would then need to be addressed by overloads.

Marking this PR as a draft until I have a working version with a protocol.
Version 2: more aligned with the documentation (any "orderable scalar" is accepted) which also breaks the Scalar-Interval dependency.

@twoertwein if you can merge master
cc @simonjayhawkins if any comments
I think that this is based on stuff I've put in the Microsoft stubs.  I recently did a PR there that addresses a few issues.  See https://github.com/microsoft/python-type-stubs/pull/167/files

One thing that I had to test was getting the type right for intervals based on `int` or `float` compared to `Timestamp`.  

I added the following test in that PR in the MS stubs:
```python
def test_interval_length() -> None:
    i1 = pd.Interval(pd.Timestamp("2000-01-01"), pd.Timestamp("2000-01-02"), closed="both")
    reveal_type(i1.length, expected_string="Timedelta")
    i1.length.total_seconds()

    i2 = pd.Interval(10, 20)
    reveal_type(i2.length, expected_type=int)

    i3 = pd.Interval(13.2, 19.5)
    reveal_type(i3.length, expected_type=float)
```
`mypy` doesn't support the `expected_type` or `expected_string` arguments to `reveal_type`, but you ought to make sure that your stubs are figuring out the above in terms of the type of `length` being dependent on the constructor for `Interval`
Thanks @Dr-Irv It definitely makes sense to add exceptions for Pandas/important stdlib types. I think I found a way to encode this (using your workaround for overload+decorators):

```py
# note: mypy doesn't support overloading properties
# based on github.com/microsoft/python-type-stubs/pull/167
class _LengthDescriptor:
    @overload
    def __get__(self, instance: IntervalMixin[Timestamp], owner: Any) -> Timedelta: ...
    @overload
    def __get__(self, instance: IntervalMixin[datetime], owner: Any) -> timedelta: ...
    @overload
    def __get__(
        self, instance: IntervalMixin[_OrderableT], owner: Any
    ) -> _OrderableT: ...

class IntervalMixin(Generic[_OrderableT]):
    ...
    length: _LengthDescriptor
```

Unfortunately, there doesn't seem to be a generic way to handle other exceptions (Interval can be used with more classes than just int/float/Timestamp).

Still need to debug this locally - getting some "has-type/misc" mypy errors and pyright doesn't pick up on it (probably an issue with TypeVar'ed Protocol in Generic classes - will create an issue on pyright).
> Unfortunately, there doesn't seem to be a generic way to handle other exceptions (Interval can be used with more classes than just int/float/Timestamp).

Not sure about that.  See below:
```
pd.Interval(datetime.datetime(year=2022,month=3, day=15), datetime.datetime(year=2022,month=4, day=15))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pandas\_libs\interval.pyx", line 325, in pandas._libs.interval.Interval.__init__
  File "pandas\_libs\interval.pyx", line 345, in pandas._libs.interval.Interval._validate_endpoint
ValueError: Only numeric, Timestamp and Timedelta endpoints are allowed when constructing an Interval.
```
So I think we don't need to worry about `datetime` endpoints.

In that case, 1) your approach might be much simpler and 2) the documentation needs to be updated.
> cc @simonjayhawkins if any comments

yep. lgtm. always happy to have PRs that add more types merged if mypy is green. 

@Dr-Irv comments and pre-commit failure outstanding.
Closing in favor of #46098