-
Notifications
You must be signed in to change notification settings - Fork 58
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
Type checking #83
Type checking #83
Conversation
This LGTM but I'm probably not the best to review since I am still a heathen that has never used Python type checking before :-) |
Same for me just before this PR 😄 |
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 is great and all the changes look good! Thanks so much for diving into it. I left some minor items to discuss on the PR.
@@ -51,7 +55,7 @@ class NotebookClient(LoggingConfigurable): | |||
), | |||
).tag(config=True) | |||
|
|||
timeout_func = Any( | |||
timeout_func: t.Any = Any( |
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.
Usually you do from typing import Any
, but this is fine
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.
Yes but some names in typing
collide with traitlets
(List
, Any
, Dict
), it's annoying.
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.
Oh... yeah good point
def __init__( | ||
self, | ||
nb: NotebookNode, | ||
km: t.Optional[KernelManager] = None, |
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.
Technically there's a KernelManagerABC.. but I don't think anyone directly inherits from the abc other than KernelManager. But it might be a good idea to change to over.
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.
@kevin-bates FYI I think we should shift the RemoteKernelManager to inherit from the KernelManagerABC so we can have a consistent type check / interface. From looking at the class I believe this wouldn't disrupt anything therein.
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.
If RemoteKernelManager inherits directly from KernelManagerABC instead of the concrete KernelManager, how does it then leverage the implementation of KernelManager?
RKM is not an independent implementation of KernelManagerABC - otherwise that's what it would inherit from. It needs to be plugged into the framework and that is accomplished by deriving from KernelManager.
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 tracked down the inheritance and it ends up at MappingKernelManager->MultiKernelManager -- which means we should instead make MultiKernelManager
inherit the KernelManagerABC
-- though the contract for MKM uses some reserved kwargs that KM does not on it's methods -- for type hinting check purposes. Or we can make this type check a union of the two classes. Drifting from the topic at hand (can move elsewhere if need be) is there a historical reason the MappingKernelManager
is in notebook over jupyter_client? Where do we draw the line on custom extensions within the jupyter github repos for kernel managers?
RKM is not an independent implementation of KernelManagerABC - otherwise that's what it would inherit from. It needs to be plugged into the framework and that is accomplished by deriving from KernelManager.
Think of KernelManagerABC as an interface mixin -- you can inherit from it and other things without issue using Python's default inheritance ordering for overlapping method names. Given the chain of existing inheritance it does reach into the jupyter_client as I pointed out above, so likely we need to just resolve that in jupyter_client without wanting/needing any changes in EG.
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.
Let's merge for now and we can open new issues if there's problems with EG patterns in combination.
nbclient/client.py
Outdated
@@ -344,13 +354,13 @@ async def _async_cleanup_kernel(self): | |||
finally: | |||
# Remove any state left over even if we failed to stop the kernel | |||
await ensure_async(self.km.cleanup()) | |||
if getattr(self, "kc"): | |||
if self.kc is not None: |
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 believe this one actually needs to be getattr(self, "kc")
or getattr(self, "kc") is not None
because the KernelGateway (and possibly other?) code paths sometimes don't assign any kc to the object by the time it reaches this call sometimes. I see you were testing this with asserts within the functions in places, but I can't quite recall the exact circumstances that cause the issue originally.
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.
So I changed it because otherwise we get error: Item "None" of "Optional[Any]" has no attribute "stop_channels"
with the following line. I think it should be:
if getattr(self, "kc") and self.kc is not None:
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.
sounds good to me
@@ -419,7 +430,7 @@ def setup_kernel(self, **kwargs): | |||
|
|||
# Can't use run_until_complete on an asynccontextmanager function :( | |||
if self.km is None: | |||
self.start_kernel_manager() | |||
self.km = self.start_kernel_manager() |
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.
Not really necessary to do the double assignment since self.start_kernel_manager()
assigns self.km regardless, but doesn't really hurt anything either for protecting subclasses not implementing the same side-effect.
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.
If we don't assign to self.km
we get error: Item "None" of "Optional[Any]" has no attribute "has_kernel"
with the following line.
msg_id: str, | ||
cell: t.Optional[NotebookNode] = None) -> t.Optional[t.Dict]: | ||
|
||
assert self.kc is not None |
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.
A little odd to keep the asserts in non-test code.
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.
Here too, without the assert we get error: Item "None" of "Optional[Any]" has no attribute "shell_channel"
later in the method, because the type checker has no idea self.kc
has a non-None
value at that time.
@@ -820,13 +879,13 @@ def output(self, outs, msg, display_id, cell_index): | |||
# default output behaviour (e.g. OutputWidget) | |||
hook = self.output_hook_stack[parent_msg_id][-1] | |||
hook.output(outs, msg, display_id, cell_index) | |||
return | |||
return None |
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.
Had to reread the PEP on this, but when returning elsewhere with a value it is recommended to have a return None
explicitly where exiting early without an object. So good catch and I'll have to remember that in the future!
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.
explicit is better than implicit! :-)
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.
Yes, this is enforced buy the type checker that complains with error: Return value expected
otherwise.
def _timeout_with_deadline(self, timeout, deadline): | ||
if deadline is not None and deadline - monotonic() < timeout: | ||
timeout = deadline - monotonic() | ||
|
||
if timeout < 0: | ||
timeout = 0 | ||
|
||
return timeout |
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.
Note that this method seemed to be never called, so I removed 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.
Huh, wonder how that happened. Probably we missed nuking it in an earlier refactor. Good catch.
Thanks @davidbrochart for knocking this out! |
No description provided.