Repository: aio-libs/aiohttp

Issue:
ClientSession.timeout has an incorrect typing
### Describe the bug

The `aiohttp.ClientSession.timeout` attribute has a type of `Union[object, aiohttp.ClientTimeout]`, however the code logic will never actually assign a bare `object` type to the `self._timeout` attribute, making this typing quite over-inclusive. Trying to use this attribute in typed code results in having to use `cast(aiohttp.ClientTimeout, session.timeout)`, which is far from ideal considering one can just fix the typing in the library.

I ran into this while using Python 3.8.10, but the exact same explanation above applies to the current master branch (and the version I'm using of course), as shown by the snippets below.

3.8 branch `__init__` parameter:
https://github.com/aio-libs/aiohttp/blob/6243204a6a6a0e5ff84ac754218381b44a841e72/aiohttp/client.py#L217

3.8 branch `self._timeout` assignment:
https://github.com/aio-libs/aiohttp/blob/6243204a6a6a0e5ff84ac754218381b44a841e72/aiohttp/client.py#L261-L290
Note the `# type: ignore` comment on `L278` there - it's because the `timeout is sentinel` check does not narrow down the `timeout` type. The correct way to go about this would be to use a `cast` there instead of ignoring the issue like that.

3.8 branch `timeout` attribute declaration:
https://github.com/aio-libs/aiohttp/blob/6243204a6a6a0e5ff84ac754218381b44a841e72/aiohttp/client.py#L1029-L1032

Master branch `__init__` parameter:
https://github.com/aio-libs/aiohttp/blob/52fa599c5637dd1a38761afb6829b0439b1cf505/aiohttp/client.py#L215

Master branch `self._timeout` assignment:
https://github.com/aio-libs/aiohttp/blob/52fa599c5637dd1a38761afb6829b0439b1cf505/aiohttp/client.py#L260-L263
Due to a different handling of the `sentinel` value via an `Enum` member, no `cast` is needed here.

Master branch `timeout` attribute declaration:
https://github.com/aio-libs/aiohttp/blob/52fa599c5637dd1a38761afb6829b0439b1cf505/aiohttp/client.py#L1008-L1011
The attribute type is still over-inclusive here though.

The solution would be quite simple:

```py
    @property
    def timeout(self) -> ClientTimeout:
        """Timeout for the session."""
        return self._timeout
````

Please let me know if you'd welcome a PR for this. I'd like to get this backported back to 3.8 (that I'm using) if possible, but if not, just fixing it in the master branch so that it's correct going forward would be good enough for me.

### To Reproduce

Utilize some kind of a type checker like MyPy.

```py
import asyncio
import aiohttp

async def main:
    session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10))
    # read back the total time attribute
    total_time = session.timeout.total  # "object" type of "Union[object, ClientTimeout]" has no attribute "total"
    print(total_time)

asyncio.run(main())
```

### Expected behavior

The attribute having only the `aiohttp.ClientTimeout` type and not requiring `cast` usage when accessing the attribute during library usage in user code.

### Logs/tracebacks

```python-traceback
Not applicable
```


### Python Version

```console
Python 3.8.10
```


### aiohttp Version

```console
Version: 3.8.1
```


### multidict Version

```console
Version: 6.0.2
```


### yarl Version

```console
Version: 1.7.2
```


### OS

Windows

### Related component

Client

### Additional context

Related issues and PRs:

#4191
#4193

### Code of Conduct

- [X] I agree to follow the aio-libs Code of Conduct


Discussion:
I'll certainly take a look over it, if you create a PR.

> The correct way to go about this would be to use a `cast` there instead of ignoring the issue like that.

A `cast()` _is_ ignoring the issue. We try to avoid both casts and ignores where possible. In many cases, if you are working around a bug/limitation of mypy, the ignore option is safer (if fixed later, you are more likely to get an unused ignore error than a redundant cast error, plus it's easier to pick out these discrepancies when looking at the code base).
> A `cast()` _is_ ignoring the issue.

A `cast` is just a "manual" way of performing type narrowing. Yes, it kinda is ignoring the issue, but it can be made more specific than an ignore comment. Whatever type you cast the variable to, has to be a subtype of the type the variable already has, otherwise MyPy returns an error (as far as I can remember). I think this is better than a `type: ignore[assignment]` comment, because a `cast` wouldn't let you assign an `int` by accident there for example, while a comment like that leaves no complaints. That's just my take on it though.

I'll try to prepare a PR later today then, or later this week if I won't be able to do so today.
> A `cast` is just a "manual" way of performing type narrowing. Whatever type you cast the variable to, has to be a subtype of the type the variable already has.

No, it literally just replaces the type, thus throwing away type safety:
```
from typing import cast
foo = "apple"
reveal_type(cast(int, foo))
```
```
> mypy --strict test.py
test.py:3: note: Revealed type is "builtins.int"
Success: no issues found in 1 source file
```

There are certainly times where casting makes more sense, but both are inherently unsafe and are ignoring the actual issue.
That's weird, I remember getting an error before when trying to straight up "replace" a type like you just shown. Strange.
I have created #6923 to address this issue.

Resolving patch:
diff --git a/aiohttp/client.py b/aiohttp/client.py
--- a/aiohttp/client.py
+++ b/aiohttp/client.py
@@ -1027,7 +1027,7 @@ def loop(self) -> asyncio.AbstractEventLoop:
         return self._loop
 
     @property
-    def timeout(self) -> Union[object, ClientTimeout]:
+    def timeout(self) -> ClientTimeout:
         """Timeout for the session."""
         return self._timeout
 


Test patch: