Skip to content

Commit

Permalink
Run mypy in strict mode
Browse files Browse the repository at this point in the history
  • Loading branch information
rec committed Jan 25, 2024
1 parent c738b3b commit 2530c6b
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 23 deletions.
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@ branch = true
source = ["sproc"]

[tool.coverage.report]
fail_under = 82
fail_under = 81
skip_covered = true

[tool.ruff]
line-length = 88

[tool.ruff.format]
quote-style = "single"

[tool.mypy]
strict = true
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
59 changes: 38 additions & 21 deletions sproc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,19 @@
returncode = sproc.log(CMD)
"""

from queue import Queue
from threading import Thread
from typing import Callable, List, Mapping, Optional, Sequence, Union
import functools
import shlex
import subprocess
import typing as t
from queue import Queue
from threading import Thread
from typing import Callable, List, Mapping, Optional, Sequence, Union

__all__ = 'Sub', 'call', 'call_in_thread', 'run', 'log'

DEFAULTS = {'stderr': subprocess.PIPE, 'stdout': subprocess.PIPE}

Callback = Optional[Callable]
Callback = Optional[t.Callable[..., t.Any]]
Cmd = Union[str, Sequence[str]]


Expand All @@ -70,7 +71,7 @@ class Sub:
"""

@functools.wraps(subprocess.Popen)
def __init__(self, cmd: Cmd, *, by_lines: bool = True, **kwargs: Mapping) -> None:
def __init__(self, cmd: Cmd, *, by_lines: bool = True, **kwargs: t.Any) -> None:
if 'stdout' in kwargs or 'stderr' in kwargs:
raise ValueError('Cannot set stdout or stderr')

Expand All @@ -91,7 +92,7 @@ def __init__(self, cmd: Cmd, *, by_lines: bool = True, **kwargs: Mapping) -> Non
def returncode(self) -> int:
return self.proc.returncode if self.proc else 0

def __iter__(self):
def __iter__(self) -> t.Iterator[t.Tuple[bool, str]]:
"""
Yields a sequence of `ok, line` pairs from `stdout` and `stderr` of
a subprocess, where `ok` is `True` if `line` came from `stdout`
Expand All @@ -100,7 +101,7 @@ def __iter__(self):
After iteration is done, the `.returncode` property contains
the error code from the subprocess, an integer where 0 means no error.
"""
queue = Queue()
queue: Queue[t.Tuple[bool, t.Optional[str]]] = Queue()

with subprocess.Popen(self.cmd, **self.kwargs) as self.proc:
for ok in False, True:
Expand Down Expand Up @@ -158,16 +159,20 @@ def call_in_thread(self, out: Callback = None, err: Callback = None) -> None:
for ok in False, True:
self._start_thread(ok, callback)

def run(self):
def run(self) -> t.Tuple[t.List[str], t.List[str], int]:
"""
Reads lines from `stdout` and `stderr` into two lists `out` and `err`,
then returns a tuple `(out, err, returncode)`
"""
out, err = [], []
out: t.List[str] = []
err: t.List[str] = []

self.call(out.append, err.append)
return out, err, self.returncode

def log(self, out: str = ' ', err: str = '! ', print: Callable = print):
def log(
self, out: str = ' ', err: str = '! ', print: t.Callable[..., None] = print
) -> int:
"""
Read lines from `stdin` and `stderr` and prints them with prefixes
Expand All @@ -181,25 +186,29 @@ def log(self, out: str = ' ', err: str = '! ', print: Callable = print):
"""
return self.call(lambda x: print(out + x), lambda x: print(err + x))

def join(self, timeout: Optional[int] = None):
def join(self, timeout: Optional[int] = None) -> None:
"""Join the stream handling threads"""
for t in self._threads:
t.join(timeout)

def kill(self):
def kill(self) -> None:
"""Kill the running process, if any"""
self.proc and self.proc.kill()
if self.proc:
self.proc.kill()

def _start_thread(self, ok, callback):
def read_stream():
def _start_thread(
self, ok: bool, callback: t.Callable[[bool, t.Optional[str]], None]
) -> None:
def read_stream() -> None:
try:
stream = self.proc.stdout if ok else self.proc.stderr
assert stream is not None
line = '.'
while line or self.proc.poll() is None:
if self.by_lines:
line = stream.readline()
else:
line = stream.read1()
line = stream.read()

if line:
if not isinstance(line, str):
Expand All @@ -212,7 +221,11 @@ def read_stream():
th.start()
self._threads.append(th)

def _callback(self, out, err):
def _callback(
self,
out: t.Optional[t.Callable[..., t.Any]],
err: t.Optional[t.Callable[..., t.Any]],
) -> t.Callable[[bool, t.Optional[str]], t.Any]:
if out and err:
return lambda ok, line: line and (out(line) if ok else err(line))
if out:
Expand All @@ -223,7 +236,7 @@ def _callback(self, out, err):
return lambda ok, line: None


def call(cmd: Cmd, out: Callback = None, err: Callback = None, **kwargs) -> int:
def call(cmd: Cmd, out: Callback = None, err: Callback = None, **kwargs: t.Any) -> int:
"""
Args:
cmd: The command to run in a subprocess
Expand All @@ -240,7 +253,7 @@ def call(cmd: Cmd, out: Callback = None, err: Callback = None, **kwargs) -> int:


def call_in_thread(
cmd: Cmd, out: Callback = None, err: Callback = None, **kwargs
cmd: Cmd, out: Callback = None, err: Callback = None, **kwargs: t.Any
) -> None:
"""
Args:
Expand All @@ -261,12 +274,16 @@ def call_in_thread(


@functools.wraps(Sub.__init__)
def run(cmd: Cmd, **kwargs) -> int:
def run(cmd: Cmd, **kwargs: t.Any) -> t.Tuple[t.List[str], t.List[str], int]:
return Sub(cmd, **kwargs).run()


def log(
cmd: Cmd, out: str = ' ', err: str = '! ', print: Callable = print, **kwargs
cmd: Cmd,
out: str = ' ',
err: str = '! ',
print: t.Callable[..., None] = print,
**kwargs: t.Any,
) -> int:
"""
Args:
Expand Down
3 changes: 2 additions & 1 deletion test_sproc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sproc
import unittest

import sproc


class RunTest(unittest.TestCase):
def setUp(self):
Expand Down

0 comments on commit 2530c6b

Please sign in to comment.