Skip to content

mypy: More annotations #796

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

Merged
merged 19 commits into from
Dec 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force

<!-- Maintainers, insert changes / features for the next release here -->

### Breaking changes

- Type annotations: Add strict mypy typings (#796)

## tmuxp 1.22.1 (2022-12-27)

_Maintenance only, no bug fixes or features_
Expand Down
13 changes: 11 additions & 2 deletions src/tmuxp/cli/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

from .utils import prompt_yes_no

if t.TYPE_CHECKING:
from typing_extensions import Literal

AllowedFileTypes = Literal["json", "yaml"]


def create_convert_subparser(
parser: argparse.ArgumentParser,
Expand Down Expand Up @@ -50,6 +55,7 @@ def command_convert(

_, ext = os.path.splitext(workspace_file)
ext = ext.lower()
to_filetype: "AllowedFileTypes"
if ext == ".json":
to_filetype = "yaml"
elif ext in [".yaml", ".yml"]:
Expand All @@ -60,8 +66,11 @@ def command_convert(
configparser = ConfigReader.from_file(workspace_file)
newfile = workspace_file.parent / (str(workspace_file.stem) + f".{to_filetype}")

export_kwargs = {"default_flow_style": False} if to_filetype == "yaml" else {}
new_workspace = configparser.dump(format=to_filetype, indent=2, **export_kwargs)
new_workspace = configparser.dump(
format=to_filetype,
indent=2,
**{"default_flow_style": False} if to_filetype == "yaml" else {},
)

if not answer_yes:
if prompt_yes_no(f"Convert to <{workspace_file}> to {to_filetype}?"):
Expand Down
13 changes: 10 additions & 3 deletions src/tmuxp/cli/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def set_layout_hook(session: Session, hook_name: str) -> None:
session.cmd(*cmd)


def load_plugins(sconf: t.Any) -> t.List[t.Any]:
def load_plugins(sconf: t.Dict[str, t.Any]) -> t.List[t.Any]:
"""
Load and return plugins in workspace
"""
Expand Down Expand Up @@ -158,6 +158,7 @@ def _reattach(builder: WorkspaceBuilder):

If not, ``tmux attach-session`` loads the client to the target session.
"""
assert builder.session is not None
for plugin in builder.plugins:
plugin.reattach(builder.session)
proc = builder.session.cmd("display-message", "-p", "'#S'")
Expand All @@ -181,6 +182,7 @@ def _load_attached(builder: WorkspaceBuilder, detached: bool) -> None:
detached : bool
"""
builder.build()
assert builder.session is not None

if "TMUX" in os.environ: # tmuxp ran from inside tmux
# unset TMUX, save it, e.g. '/tmp/tmux-1000/default,30668,0'
Expand Down Expand Up @@ -214,6 +216,8 @@ def _load_detached(builder: WorkspaceBuilder) -> None:
"""
builder.build()

assert builder.session is not None

if has_gte_version("2.6"): # prepare for both cases
set_layout_hook(builder.session, "client-attached")
set_layout_hook(builder.session, "client-session-changed")
Expand All @@ -231,6 +235,7 @@ def _load_append_windows_to_current_session(builder: WorkspaceBuilder) -> None:
"""
current_attached_session = builder.find_current_attached_session()
builder.build(current_attached_session, append=True)
assert builder.session is not None
if has_gte_version("2.6"): # prepare for both cases
set_layout_hook(builder.session, "client-attached")
set_layout_hook(builder.session, "client-session-changed")
Expand All @@ -244,6 +249,7 @@ def _setup_plugins(builder: WorkspaceBuilder) -> Session:
----------
builder: :class:`workspace.builder.WorkspaceBuilder`
"""
assert builder.session is not None
for plugin in builder.plugins:
plugin.before_script(builder.session)

Expand Down Expand Up @@ -458,8 +464,9 @@ def load_workspace(
)

if choice == "k":
builder.session.kill_session()
tmuxp_echo("Session killed.")
if builder.session is not None:
builder.session.kill_session()
tmuxp_echo("Session killed.")
elif choice == "a":
_reattach(builder)
else:
Expand Down
10 changes: 5 additions & 5 deletions src/tmuxp/config_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ class ConfigReader:
'{\n "session_name": "my session"\n}'
"""

def __init__(self, content: "RawConfigData"):
def __init__(self, content: "RawConfigData") -> None:
self.content = content

@staticmethod
def _load(format: "FormatLiteral", content: str):
def _load(format: "FormatLiteral", content: str) -> t.Dict[str, t.Any]:
"""Load raw config data and directly return it.

>>> ConfigReader._load("json", '{ "session_name": "my session" }')
Expand All @@ -46,7 +46,7 @@ def _load(format: "FormatLiteral", content: str):
raise NotImplementedError(f"{format} not supported in configuration")

@classmethod
def load(cls, format: "FormatLiteral", content: str):
def load(cls, format: "FormatLiteral", content: str) -> "ConfigReader":
"""Load raw config data into a ConfigReader instance (to dump later).

>>> cfg = ConfigReader.load("json", '{ "session_name": "my session" }')
Expand All @@ -69,7 +69,7 @@ def load(cls, format: "FormatLiteral", content: str):
)

@classmethod
def _from_file(cls, path: pathlib.Path):
def _from_file(cls, path: pathlib.Path) -> t.Dict[str, t.Any]:
r"""Load data from file path directly to dictionary.

**YAML file**
Expand Down Expand Up @@ -114,7 +114,7 @@ def _from_file(cls, path: pathlib.Path):
)

@classmethod
def from_file(cls, path: pathlib.Path):
def from_file(cls, path: pathlib.Path) -> "ConfigReader":
r"""Load data from file path

**YAML file**
Expand Down
8 changes: 6 additions & 2 deletions src/tmuxp/exc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
~~~~~~~~~

"""
import typing as t

from ._compat import implements_to_string


Expand All @@ -28,7 +30,7 @@ class TmuxpPluginException(TmuxpException):


class BeforeLoadScriptNotExists(OSError):
def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

self.strerror = "before_script file '%s' doesn't exist." % self.strerror
Expand All @@ -41,7 +43,9 @@ class BeforeLoadScriptError(Exception):
:meth:`tmuxp.util.run_before_script`.
"""

def __init__(self, returncode, cmd, output=None):
def __init__(
self, returncode: int, cmd: str, output: t.Optional[str] = None
) -> None:
self.returncode = returncode
self.cmd = cmd
self.output = output
Expand Down
124 changes: 64 additions & 60 deletions src/tmuxp/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
}


def setup_logger(logger=None, level="INFO"):
def setup_logger(
logger: t.Optional[logging.Logger] = None, level: str = "INFO"
) -> None:
"""
Setup logging for CLI use.

Expand All @@ -49,80 +51,82 @@ def setup_logger(logger=None, level="INFO"):


def set_style(
message, stylized, style_before=None, style_after=None, prefix="", suffix=""
):
message: str,
stylized: bool,
style_before: str = "",
style_after: str = "",
prefix: str = "",
suffix: str = "",
) -> str:
if stylized:
return prefix + style_before + message + style_after + suffix

return prefix + message + suffix


def default_log_template(
self: t.Type[logging.Formatter],
record: logging.LogRecord,
stylized: t.Optional[bool] = False,
**kwargs: t.Any,
) -> str:
"""
Return the prefix for the log message. Template for Formatter.

Parameters
----------
:py:class:`logging.LogRecord` :
object. this is passed in from inside the
:py:meth:`logging.Formatter.format` record.

Returns
-------
str
template for logger message
"""

reset = Style.RESET_ALL
levelname = set_style(
"(%(levelname)s)",
stylized,
style_before=(LEVEL_COLORS.get(record.levelname, "") + Style.BRIGHT),
style_after=Style.RESET_ALL,
suffix=" ",
)
asctime = set_style(
"%(asctime)s",
stylized,
style_before=(Fore.BLACK + Style.DIM + Style.BRIGHT),
style_after=(Fore.RESET + Style.RESET_ALL),
prefix="[",
suffix="]",
)
name = set_style(
"%(name)s",
stylized,
style_before=(Fore.WHITE + Style.DIM + Style.BRIGHT),
style_after=(Fore.RESET + Style.RESET_ALL),
prefix=" ",
suffix=" ",
)

if stylized:
return reset + levelname + asctime + name + reset

return levelname + asctime + name


class LogFormatter(logging.Formatter):
template = default_log_template

def __init__(self, color=True, *args, **kwargs):
def template(
self: logging.Formatter,
record: logging.LogRecord,
stylized: bool = False,
**kwargs: t.Any,
) -> str:
"""
Return the prefix for the log message. Template for Formatter.

Parameters
----------
:py:class:`logging.LogRecord` :
object. this is passed in from inside the
:py:meth:`logging.Formatter.format` record.

Returns
-------
str
template for logger message
"""
reset = Style.RESET_ALL
levelname = set_style(
"(%(levelname)s)",
stylized,
style_before=(LEVEL_COLORS.get(record.levelname, "") + Style.BRIGHT),
style_after=Style.RESET_ALL,
suffix=" ",
)
asctime = set_style(
"%(asctime)s",
stylized,
style_before=(Fore.BLACK + Style.DIM + Style.BRIGHT),
style_after=(Fore.RESET + Style.RESET_ALL),
prefix="[",
suffix="]",
)
name = set_style(
"%(name)s",
stylized,
style_before=(Fore.WHITE + Style.DIM + Style.BRIGHT),
style_after=(Fore.RESET + Style.RESET_ALL),
prefix=" ",
suffix=" ",
)

if stylized:
return reset + levelname + asctime + name + reset

return levelname + asctime + name

def __init__(self, color: bool = True, *args, **kwargs) -> None:
logging.Formatter.__init__(self, *args, **kwargs)

def format(self, record):
def format(self, record: logging.LogRecord) -> str:
try:
record.message = record.getMessage()
except Exception as e:
record.message = f"Bad message ({e!r}): {record.__dict__!r}"

date_format = "%H:%m:%S"
record.asctime = time.strftime(date_format, self.converter(record.created))
formatting = self.converter(record.created) # type:ignore
record.asctime = time.strftime(date_format, formatting)

prefix = self.template(record) % record.__dict__

Expand Down
Loading