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

Type checking #83

Merged
merged 12 commits into from
Jun 22, 2020
Merged

Type checking #83

merged 12 commits into from
Jun 22, 2020

Conversation

davidbrochart
Copy link
Member

No description provided.

@davidbrochart davidbrochart mentioned this pull request Jun 19, 2020
@davidbrochart davidbrochart changed the title [WIP] Type checking Type checking Jun 19, 2020
@choldgraf
Copy link
Contributor

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 :-)

@davidbrochart
Copy link
Member Author

Same for me just before this PR 😄

Copy link
Contributor

@MSeal MSeal left a 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(
Copy link
Contributor

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

Copy link
Member Author

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.

Copy link
Contributor

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,
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member

@kevin-bates kevin-bates Jun 19, 2020

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.

Copy link
Contributor

@MSeal MSeal Jun 19, 2020

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.

Copy link
Contributor

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.

@@ -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:
Copy link
Contributor

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.

Copy link
Member Author

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:

Copy link
Contributor

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()
Copy link
Contributor

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.

Copy link
Member Author

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
Copy link
Contributor

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.

Copy link
Member Author

@davidbrochart davidbrochart Jun 19, 2020

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
Copy link
Contributor

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!

Copy link
Contributor

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! :-)

Copy link
Member Author

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.

Comment on lines -650 to -657
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
Copy link
Member Author

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.

Copy link
Contributor

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.

@MSeal MSeal merged commit 202e046 into jupyter:master Jun 22, 2020
@MSeal
Copy link
Contributor

MSeal commented Jun 22, 2020

Thanks @davidbrochart for knocking this out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants