-
Notifications
You must be signed in to change notification settings - Fork 38
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
Add types to modal.functions #1328
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Auto-approved 👍. This diff qualified for automatic approval and doesn't need follow up review.
modal/functions.py
Outdated
@@ -1386,7 +1387,7 @@ async def get_current_stats(self) -> FunctionStats: | |||
Function = synchronize_api(_Function) | |||
|
|||
|
|||
class _FunctionCall(_Object, type_prefix="fc"): | |||
class _FunctionCall(_Object, type_prefix="fc"): # type: ignore[call-arg] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm less familiar with type_prefix
, but on a grep looks like it adds a two char prefix to objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@freider I've removed this. I was getting mypy error previously but might also have been when I was doing mypy checks before running inv etc.
Hi there! I add types to open source projects for funsies, recently for instructor. If this is helpful, I can create an issue, start from upstream nodes and work my way downstream, in addition to including CI checks for the files changed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @savarin — we already have type annotations in our client library, and we run type-checking in CI with mypy .
This is passing already so we're curious how you're thinking about going through this or any best practices you were following to edit these types?
_current_input_id = ContextVar("_current_input_id") | ||
_current_function_call_id = ContextVar("_current_function_call_id") | ||
_current_input_id: ContextVar = ContextVar("_current_input_id") | ||
_current_function_call_id: ContextVar = ContextVar("_current_function_call_id") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type annotation conflicts with our auto-generated inv type-stubs
machinery, which generates type stubs for the client library. That's it was omitted. Not sure what the difference between omitting and having it is here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also less clear why this is raised; I usually see mypy requiring a declaration over compound data structures like lists and dictionaries (say needing List[bool]
and Dict[str, int]
vs List
and Dict
). Let me try getting inv mypy
to pass locally and see what else I find.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An explanation as for why this makes a difference:
We use synchronicity's type-stub generation to emit .pyi-files for these files in order to get type info for the runtime-generated blocking/async interfaces of Function
and not just the internal _Function
type.
To do that, synchronicity reads annotations, conditionally transforms them, and then re-emits them in the output .pyi-files. In the case of module globals they are only emitted in the .pyi-file if they are explicitly annotated in the original file, so without this annotation here these variables aren't even included in the .pyi-files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it! My understanding here is to incorporate these changes, let me know otherwise.
That being said, I do see these in my .pyi
file (after running inv protoc
and inv type-stubs
:
_current_input_id: _contextvars.ContextVar
_current_function_call_id: _contextvars.ContextVar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those should be fine
@@ -924,7 +925,7 @@ def from_parametrized( | |||
obj, | |||
from_other_workspace: bool, | |||
options: Optional[api_pb2.FunctionOptions], | |||
args: Iterable[Any], | |||
args: Sized, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't heard of this before, what is the difference between Iterable[Any]
-> Sized
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Iterable
supports any object that can be iterated over (implements __iter__
) vs Sized
supports any object that has a length (implements __len__
). Generator can be iterated over but will fail this line here.
Simple example:
def custom_print(lines: Iterable[Any]) -> None:
for line in lines:
print(line) # OK
def custom_len(lines: Iterable[Any]) -> None:
print(len(lines)) # run.py:10: error: Argument 1 to "len" has incompatible type "Iterable[Any]"; expected "Sized" [arg-type]
Fix:
def custom_len(lines: Sized) -> None:
print(len(lines)) # OK
@ekzhang Got it. Let me try getting the mypy checks in CI to pass locally, using this successful Github Actions run as example. In the meantime, I can share that running locally The errors raised appear directionally correct, for example the |
@ekzhang OK cool got I haven't done much type checking against For example, in the class FunctionEnv:
...
def __init__(self, image: typing.Union[modal.image._Image, None], mounts: typing.Sequence[modal.mount._Mount], ...) -> None:
... However suppose a class FunctionEnv:
...
def __init__(self, image: typing.Union[modal.image._Image, None], mounts: typing.Sequence[modal.mount._Mount], ...) -> None:
mounts.append(modal.mount._Mount(...)) Now whether checking I just wanted to raise my hand to offer to help out checking that implementation is typed correctly, should this be desired. What I've done in the past is add the types file-by-file, and ensure CI only runs on the files that have been typed. |
16ba69a
to
16ed523
Compare
First, thanks so much for contributing here! Next, with this in mind we're looking into the differences in type annotations between the |
OK I see this now - there's a bit more 'magic' here that I expected (with stubs codegen from protos etc), very cool though. I was curious what the difference was between
Sounds good, will keep an eye out for changes! |
modal/functions.py
Outdated
@@ -1260,7 +1263,7 @@ async def remote_gen(self, *args, **kwargs) -> AsyncGenerator[Any, None]: | |||
async for item in self._call_generator(args, kwargs): # type: ignore | |||
yield item | |||
|
|||
def call(self, *args, **kwargs) -> Awaitable[Any]: | |||
def call(self, *args, **kwargs) -> Awaitable[Any]: # type: ignore[return] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this added? It also won't get transferred to the generated type stubs in the pyi-file so it shouldn't make a difference
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mypy
sees this function as returning None
. This doesn't raise error when we type check against stubs but does so when we type check the implementation (which this PR is suggesting we do).
To avoid confusion I'll move this PR back to draft (though also happy to close it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see now that it's the deprecated/non-function call
method you're changing. We might as well just remove the return type (or change it to None
). No need to close the PR, I think it's valuable and we'll merge it as soon as we've ironed out the questionmarks :)
I was quite surprised that mypy appears to entirely skip the implementation checks since we don't directly refer to our I'd be happy to merge this if we get rid of the # type: ignore additions which I don't think are needed? (I seem to get no mypy warnings for the functions.py when removing them and the incorrect return type on |
@freider Glad to hear this is helpful! I've since removed the type ignores, agreed no longer seeing errors now. I've seen codebases running |
5c29784
to
3c855c7
Compare
I think that's unrelated to your change - I'm getting that warning too when I run mypy locally on main. It's due to the mypy version of the repo being pinned to version ~1.4, and github actions caching has probably cached an older version than 1.8 that I have locally which triggers this warning. Should upgrade mypy in the requirements file too, but I can do that separately |
@freider I'm afraid I can't merge (I don't have write access), let me know if there's anything else I can do to be helpful. Thanks! |
Thanks for the contribution, it's merged now! |
@freider Would it be helpful if I did similar changes to other files? |
@savarin Definitely, much appreciated! :) |
Describe your changes
This PR adds mypy types to
functions.py
,mypy --follow-imports=skip modal/functions.py
will now pass.