Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use mypy strict #2180

Merged
merged 32 commits into from
Jul 23, 2023
Merged

Use mypy strict #2180

merged 32 commits into from
Jul 23, 2023

Conversation

Viicos
Copy link
Contributor

@Viicos Viicos commented Jun 10, 2023

Fixes #2153.

mypy config updated:

  • no_implicit_optional is now the default (and seems to have been replaced by implicit_optional)
  • disallow_untyped_defs is included by strict

Checklist

  • I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.

starlette/config.py Outdated Show resolved Hide resolved
Copy link
Member

@adriangb adriangb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just a few small comments

starlette/concurrency.py Outdated Show resolved Hide resolved
starlette/config.py Outdated Show resolved Hide resolved
starlette/middleware/__init__.py Show resolved Hide resolved
starlette/middleware/wsgi.py Show resolved Hide resolved
@adriangb
Copy link
Member

Please fix errors

@Viicos
Copy link
Contributor Author

Viicos commented Jun 10, 2023

Please fix errors

This is still WIP, I'm doing it in small parts to avoid having a single big commit

@adriangb
Copy link
Member

Ok no worries, I’ll just remove the approval until you’re ready

Copy link
Member

@adriangb adriangb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing approval until this is marked as ready for review

"ImmutableMultiDict",
typing.Mapping,
"ImmutableMultiDict[typing.Any, typing.Any]",
typing.Mapping[typing.Any, typing.Any],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the signature of the ImmutableMultiDict's __init__ method:

class ImmutableMultiDict(typing.Mapping[_KeyType, _CovariantValueType]):
    _dict: typing.Dict[_KeyType, _CovariantValueType]

    def __init__(
        self,
        *args: typing.Union[
            "ImmutableMultiDict[_KeyType, _CovariantValueType]",
            typing.Mapping[_KeyType, _CovariantValueType],
            typing.Iterable[typing.Tuple[_KeyType, _CovariantValueType]],
        ],
        **kwargs: typing.Any,
    ) -> None:

We should parametrize these ones as [str, str], but the following happens a few lines underneath:

self._list = [(str(k), str(v)) for k, v in self._list]
self._dict = {str(k): str(v) for k, v in self._dict.items()}

directory: typing.Union[
str, PathLike, typing.Sequence[typing.Union[str, PathLike]]
],
directory: "typing.Union[str, PathLike[typing.AnyStr], typing.Sequence[typing.Union[str, PathLike[typing.AnyStr]]]]",
Copy link
Contributor Author

@Viicos Viicos Jun 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is PathLike[AnyStr] fine? Or we could enforce str paths. I often see PathLike[Any] but doesn't feel right to me as PathLike is generic wrt TypeVar("AnyStr_co", str, bytes, covariant=True)

@Kludex
Copy link
Sponsor Member

Kludex commented Jun 20, 2023

@Viicos Do you need any help here?

@Kludex Kludex added the typing Type annotations or mypy issues label Jun 20, 2023
@Viicos
Copy link
Contributor Author

Viicos commented Jun 20, 2023

Hi @Kludex thanks for asking. The main blocking point is handling callables and coroutines together, as stated in #2180 (comment). The solution I provide should work but isn't pretty, and will add a lot of overhead to the code (considering this pattern is used a lot in the starlette codebase). Otherwise we could use Any as a return type, it seems to work.

Apart from that I'll be able to fix the remaining mypy issues

@Kludex
Copy link
Sponsor Member

Kludex commented Jun 20, 2023

Hi @Kludex thanks for asking. The main blocking point is handling callables and coroutines together, as stated in #2180 (comment). The solution I provide should work but isn't pretty, and will add a lot of overhead to the code (considering this pattern is used a lot in the starlette codebase). Otherwise we could use Any as a return type, it seems to work.

Apart from that I'll be able to fix the remaining mypy issues

Any is good for now. If you make the pipeline to pass I can review it. 👍

@Kludex Kludex added this to the Version 1.0 milestone Jun 20, 2023
@Viicos
Copy link
Contributor Author

Viicos commented Jun 20, 2023

Any is good for now. If you make the pipeline to pass I can review it. 👍

Will be ready for review by the end of the week, as I can see 1.0.0 is coming soon

starlette/concurrency.py Outdated Show resolved Hide resolved
@Kludex
Copy link
Sponsor Member

Kludex commented Jun 22, 2023

Looks like the overrides from mypy is not working properly? 🤔

@Viicos
Copy link
Contributor Author

Viicos commented Jun 23, 2023

Mypy errors left:

starlette/testclient.py:383: error: Name "httpx._client.CookieTypes" is not defined  [name-defined]
...
starlette/testclient.py:699: error: Name "httpx._client.TimeoutTypes" is not defined  [name-defined]

I have no idea why they happen -> 8811c1c

Looks like the overrides from mypy is not working properly? 🤔

starlette/pyproject.toml

Lines 68 to 71 in 1be57fd

[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
check_untyped_defs = true

This one has no effect, module isn't working with a plain directory, instead it needs a proper module to import. For reference: python/mypy#10045
This doesn't seem trivial

@Viicos Viicos marked this pull request as ready for review June 23, 2023 20:30
@@ -23,10 +29,14 @@ def requires(
scopes: typing.Union[str, typing.Sequence[str]],
status_code: int = 403,
redirect: typing.Optional[str] = None,
) -> typing.Callable[[_CallableType], _CallableType]:
) -> typing.Callable[
[typing.Callable[_P, typing.Any]], typing.Callable[_P, typing.Any]
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for not using the following?

Suggested change
[typing.Callable[_P, typing.Any]], typing.Callable[_P, typing.Any]
[typing.Callable[_P, _T]], typing.Callable[_P, _T]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't make it work as it can be async or sync (same issue as #2180 (comment)) :/

starlette/types.py Outdated Show resolved Hide resolved
starlette/testclient.py Show resolved Hide resolved
starlette/staticfiles.py Outdated Show resolved Hide resolved
@@ -773,7 +783,7 @@ def host(
def add_route(
self,
path: str,
endpoint: typing.Callable,
endpoint: typing.Callable[..., typing.Any],
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't an endpoint always Callable[[Request], typing.Any]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went for Callable[..., Any] as this endpoint argument is passed to routing.Route, which is doing the following:

def __init__(
self,
path: str,
endpoint: typing.Callable,
*,
methods: typing.Optional[typing.List[str]] = None,
name: typing.Optional[str] = None,
include_in_schema: bool = True,
) -> None:
assert path.startswith("/"), "Routed paths must start with '/'"
self.path = path
self.endpoint = endpoint
self.name = get_name(endpoint) if name is None else name
self.include_in_schema = include_in_schema
endpoint_handler = endpoint
while isinstance(endpoint_handler, functools.partial):
endpoint_handler = endpoint_handler.func
if inspect.isfunction(endpoint_handler) or inspect.ismethod(endpoint_handler):
# Endpoint is function or method. Treat it as `func(request) -> response`.
self.app = request_response(endpoint)
if methods is None:
methods = ["GET"]
else:
# Endpoint is a class. Treat it as ASGI.
self.app = endpoint

So it seems endpoint can also be a ASGIApp (+ there's a bunch of things related to functools.partial)

self, path: str, endpoint: typing.Callable, name: typing.Optional[str] = None
self,
path: str,
endpoint: typing.Callable[..., typing.Any],
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't an endpoint always Callable[[WebSocket], typing.Any]?

Copy link
Contributor Author

@Viicos Viicos Jul 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #2180 (comment) (same for #2180 (comment), #2180 (comment) if I'm not mistaken)

self, path: str, endpoint: typing.Callable, *, name: typing.Optional[str] = None
self,
path: str,
endpoint: typing.Callable[..., typing.Any],
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as comments below.

@@ -209,7 +209,7 @@ class Route(BaseRoute):
def __init__(
self,
path: str,
endpoint: typing.Callable,
endpoint: typing.Callable[..., typing.Any],
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as comments below.

starlette/requests.py Outdated Show resolved Hide resolved
Comment on lines +140 to +142
handler: typing.Optional[
typing.Callable[[Request, Exception], typing.Any]
] = None,
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this supposed to be ExceptionHandler as well? It's a question, not a suggestion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is, this was discussed earlier: #2180 (comment). We could keep it as Any, or use Union[Reponse, Awaitable[Response]] and then add some casts or type: ignore

@Kludex Kludex mentioned this pull request Jul 6, 2023
3 tasks
@Kludex
Copy link
Sponsor Member

Kludex commented Jul 6, 2023

Suggestions on what to do here? 😅

It looks like we are blocked by python/mypy#10045?

@Viicos
Copy link
Contributor Author

Viicos commented Jul 6, 2023

It looks like we are blocked by python/mypy#10045?

I guess so. Maybe we could edit the check scripts to run mypy a second time on the tests directory, but I don't know if it will work as excepted.

@Kludex
Copy link
Sponsor Member

Kludex commented Jul 13, 2023

I've asked on the python/typing Gitter to see if someone has a good alternative. 👀

@Viicos
Copy link
Contributor Author

Viicos commented Jul 13, 2023

In the meanwhile, I'll see if using a Union is feasible for #2180 (comment)

@Viicos
Copy link
Contributor Author

Viicos commented Jul 16, 2023

@Kludex let me know if the check script tweak is ok 🙂

pyproject.toml Outdated Show resolved Hide resolved
scripts/check Outdated Show resolved Hide resolved
Copy link
Sponsor Member

@Kludex Kludex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Viicos 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
typing Type annotations or mypy issues
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use mypy strict setup
3 participants