Skip to content

Commit

Permalink
Add initial typing
Browse files Browse the repository at this point in the history
The typing is still not optimal and not ready to be exposed, but can
serve as an initial basis.
  • Loading branch information
bluetech committed Feb 24, 2024
1 parent cbcd78c commit c58d485
Show file tree
Hide file tree
Showing 27 changed files with 1,152 additions and 724 deletions.
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ repos:
rev: 'v1.8.0'
hooks:
- id: mypy
additional_dependencies:
- pytest
- types-pywin32
- types-gevent
2 changes: 1 addition & 1 deletion doc/example/redirect_remote_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def write(data):
print("received:", repr(data))


outchan.setcallback(write)
outchan.setcallback(write) # type: ignore[attr-defined]

gw.remote_exec(
"""
Expand Down
3 changes: 1 addition & 2 deletions doc/example/sysinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

import execnet


parser = optparse.OptionParser(usage=__doc__)
parser.add_option(
"-f",
Expand Down Expand Up @@ -129,7 +128,7 @@ def getinfo(sshname, ssh_config=None, loginfo=sys.stdout):
if ssh_config:
spec = f"ssh=-F {ssh_config} {sshname}"
else:
spec += "ssh=%s" % sshname
spec = "ssh=%s" % sshname
debug("connecting to", repr(spec))
try:
gw = execnet.makegateway(spec)
Expand Down
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,18 @@ include = [

[tool.mypy]
python_version = "3.8"
mypy_path = ["src"]
files = ["src", "testing"]
strict = true
warn_unreachable = true
warn_unused_ignores = false
disallow_untyped_calls = false
disallow_untyped_defs = false
disallow_incomplete_defs = false

[[tool.mypy.overrides]]
module = [
"eventlet.*",
"gevent.thread.*",
]
ignore_missing_imports = true
61 changes: 42 additions & 19 deletions src/execnet/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,57 @@
gateway code for initiating popen, socket and ssh connections.
(c) 2004-2013, Holger Krekel and others
"""
from __future__ import annotations

import inspect
import linecache
import os
import textwrap
import types
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable

import execnet

from . import gateway_base
from .gateway_base import IO
from .gateway_base import Channel
from .gateway_base import Message
from .multi import Group
from .xspec import XSpec

importdir = os.path.dirname(os.path.dirname(execnet.__file__))


class Gateway(gateway_base.BaseGateway):
"""Gateway to a local or remote Python Interpreter."""

def __init__(self, io, spec):
_group: Group

def __init__(self, io: IO, spec: XSpec) -> None:
super().__init__(io=io, id=spec.id, _startcount=1)
self.spec = spec
self._initreceive()

@property
def remoteaddress(self):
return self._io.remoteaddress
def remoteaddress(self) -> str:
# Only defined for remote IO types.
return self._io.remoteaddress # type: ignore[attr-defined,no-any-return]

def __repr__(self):
def __repr__(self) -> str:
"""return string representing gateway type and status."""
try:
r = self.hasreceiver() and "receive-live" or "not-receiving"
i = len(self._channelfactory.channels())
r: str = self.hasreceiver() and "receive-live" or "not-receiving"
i = str(len(self._channelfactory.channels()))
except AttributeError:
r = "uninitialized"
i = "no"
return "<{} id={!r} {}, {} model, {} active channels>".format(
self.__class__.__name__, self.id, r, self.execmodel.backend, i
)

def exit(self):
def exit(self) -> None:
"""trigger gateway exit. Defer waiting for finishing
of receiver-thread and subprocess activity to when
group.terminate() is called.
Expand All @@ -59,7 +71,9 @@ def exit(self):
self._trace("io-error: could not send termination sequence")
self._trace(" exception: %r" % exc)

def reconfigure(self, py2str_as_py3str=True, py3str_as_py2str=False):
def reconfigure(
self, py2str_as_py3str: bool = True, py3str_as_py2str: bool = False
) -> None:
"""
set the string coercion for this gateway
the default is to try to convert py2 str as py3 str,
Expand All @@ -69,7 +83,7 @@ def reconfigure(self, py2str_as_py3str=True, py3str_as_py2str=False):
data = gateway_base.dumps_internal(self._strconfig)
self._send(Message.RECONFIGURE, data=data)

def _rinfo(self, update=False):
def _rinfo(self, update: bool = False) -> RInfo:
"""return some sys/env information from remote."""
if update or not hasattr(self, "_cache_rinfo"):
ch = self.remote_exec(rinfo_source)
Expand All @@ -79,11 +93,11 @@ def _rinfo(self, update=False):
ch.waitclose()
return self._cache_rinfo

def hasreceiver(self):
def hasreceiver(self) -> bool:
"""return True if gateway is able to receive data."""
return self._receivepool.active_count() > 0

def remote_status(self):
def remote_status(self) -> RemoteStatus:
"""return information object about remote execution status."""
channel = self.newchannel()
self._send(Message.STATUS, channel.id)
Expand All @@ -93,7 +107,11 @@ def remote_status(self):
self._channelfactory._local_close(channel.id)
return RemoteStatus(statusdict)

def remote_exec(self, source, **kwargs):
def remote_exec(
self,
source: str | types.FunctionType | Callable[..., object] | types.ModuleType,
**kwargs: object,
) -> Channel:
"""return channel object and connect it to a remote
execution thread where the given ``source`` executes.
Expand All @@ -113,7 +131,7 @@ def remote_exec(self, source, **kwargs):
file_name = None
if isinstance(source, types.ModuleType):
file_name = inspect.getsourcefile(source)
linecache.updatecache(file_name)
linecache.updatecache(file_name) # type: ignore[arg-type]
source = inspect.getsource(source)
elif isinstance(source, types.FunctionType):
call_name = source.__name__
Expand All @@ -133,24 +151,29 @@ def remote_exec(self, source, **kwargs):
)
return channel

def remote_init_threads(self, num=None):
def remote_init_threads(self, num: int | None = None) -> None:
"""DEPRECATED. Is currently a NO-OPERATION already."""
print("WARNING: remote_init_threads()" " is a no-operation in execnet-1.2")


class RInfo:
def __init__(self, kwargs):
def __init__(self, kwargs) -> None:
self.__dict__.update(kwargs)

def __repr__(self):
def __repr__(self) -> str:
info = ", ".join(f"{k}={v}" for k, v in sorted(self.__dict__.items()))
return "<RInfo %r>" % info

if TYPE_CHECKING:

def __getattr__(self, name: str) -> Any:
...


RemoteStatus = RInfo


def rinfo_source(channel):
def rinfo_source(channel) -> None:
import os
import sys

Expand All @@ -165,7 +188,7 @@ def rinfo_source(channel):
)


def _find_non_builtin_globals(source, codeobj):
def _find_non_builtin_globals(source: str, codeobj: types.CodeType) -> list[str]:
import ast
import builtins

Expand All @@ -179,7 +202,7 @@ def _find_non_builtin_globals(source, codeobj):
]


def _source_of_function(function):
def _source_of_function(function: types.FunctionType | Callable[..., object]) -> str:
if function.__name__ == "<lambda>":
raise ValueError("can't evaluate lambda functions'")
# XXX: we dont check before remote instantiation
Expand Down
Loading

0 comments on commit c58d485

Please sign in to comment.