Skip to content

Commit 0d744bb

Browse files
authored
mypy: Strict annotations (#796)
Improve typing rules and thereby completions dramatically.
2 parents b14fcb4 + 86b044f commit 0d744bb

26 files changed

+392
-192
lines changed

CHANGES

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force
1919

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

22+
### Breaking changes
23+
24+
- Type annotations: Add strict mypy typings (#796)
25+
2226
## tmuxp 1.22.1 (2022-12-27)
2327

2428
_Maintenance only, no bug fixes or features_

src/tmuxp/cli/convert.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88

99
from .utils import prompt_yes_no
1010

11+
if t.TYPE_CHECKING:
12+
from typing_extensions import Literal
13+
14+
AllowedFileTypes = Literal["json", "yaml"]
15+
1116

1217
def create_convert_subparser(
1318
parser: argparse.ArgumentParser,
@@ -50,6 +55,7 @@ def command_convert(
5055

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

63-
export_kwargs = {"default_flow_style": False} if to_filetype == "yaml" else {}
64-
new_workspace = configparser.dump(format=to_filetype, indent=2, **export_kwargs)
69+
new_workspace = configparser.dump(
70+
format=to_filetype,
71+
indent=2,
72+
**{"default_flow_style": False} if to_filetype == "yaml" else {},
73+
)
6574

6675
if not answer_yes:
6776
if prompt_yes_no(f"Convert to <{workspace_file}> to {to_filetype}?"):

src/tmuxp/cli/load.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def set_layout_hook(session: Session, hook_name: str) -> None:
107107
session.cmd(*cmd)
108108

109109

110-
def load_plugins(sconf: t.Any) -> t.List[t.Any]:
110+
def load_plugins(sconf: t.Dict[str, t.Any]) -> t.List[t.Any]:
111111
"""
112112
Load and return plugins in workspace
113113
"""
@@ -158,6 +158,7 @@ def _reattach(builder: WorkspaceBuilder):
158158
159159
If not, ``tmux attach-session`` loads the client to the target session.
160160
"""
161+
assert builder.session is not None
161162
for plugin in builder.plugins:
162163
plugin.reattach(builder.session)
163164
proc = builder.session.cmd("display-message", "-p", "'#S'")
@@ -181,6 +182,7 @@ def _load_attached(builder: WorkspaceBuilder, detached: bool) -> None:
181182
detached : bool
182183
"""
183184
builder.build()
185+
assert builder.session is not None
184186

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

219+
assert builder.session is not None
220+
217221
if has_gte_version("2.6"): # prepare for both cases
218222
set_layout_hook(builder.session, "client-attached")
219223
set_layout_hook(builder.session, "client-session-changed")
@@ -231,6 +235,7 @@ def _load_append_windows_to_current_session(builder: WorkspaceBuilder) -> None:
231235
"""
232236
current_attached_session = builder.find_current_attached_session()
233237
builder.build(current_attached_session, append=True)
238+
assert builder.session is not None
234239
if has_gte_version("2.6"): # prepare for both cases
235240
set_layout_hook(builder.session, "client-attached")
236241
set_layout_hook(builder.session, "client-session-changed")
@@ -244,6 +249,7 @@ def _setup_plugins(builder: WorkspaceBuilder) -> Session:
244249
----------
245250
builder: :class:`workspace.builder.WorkspaceBuilder`
246251
"""
252+
assert builder.session is not None
247253
for plugin in builder.plugins:
248254
plugin.before_script(builder.session)
249255

@@ -458,8 +464,9 @@ def load_workspace(
458464
)
459465

460466
if choice == "k":
461-
builder.session.kill_session()
462-
tmuxp_echo("Session killed.")
467+
if builder.session is not None:
468+
builder.session.kill_session()
469+
tmuxp_echo("Session killed.")
463470
elif choice == "a":
464471
_reattach(builder)
465472
else:

src/tmuxp/config_reader.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ class ConfigReader:
2222
'{\n "session_name": "my session"\n}'
2323
"""
2424

25-
def __init__(self, content: "RawConfigData"):
25+
def __init__(self, content: "RawConfigData") -> None:
2626
self.content = content
2727

2828
@staticmethod
29-
def _load(format: "FormatLiteral", content: str):
29+
def _load(format: "FormatLiteral", content: str) -> t.Dict[str, t.Any]:
3030
"""Load raw config data and directly return it.
3131
3232
>>> ConfigReader._load("json", '{ "session_name": "my session" }')
@@ -46,7 +46,7 @@ def _load(format: "FormatLiteral", content: str):
4646
raise NotImplementedError(f"{format} not supported in configuration")
4747

4848
@classmethod
49-
def load(cls, format: "FormatLiteral", content: str):
49+
def load(cls, format: "FormatLiteral", content: str) -> "ConfigReader":
5050
"""Load raw config data into a ConfigReader instance (to dump later).
5151
5252
>>> cfg = ConfigReader.load("json", '{ "session_name": "my session" }')
@@ -69,7 +69,7 @@ def load(cls, format: "FormatLiteral", content: str):
6969
)
7070

7171
@classmethod
72-
def _from_file(cls, path: pathlib.Path):
72+
def _from_file(cls, path: pathlib.Path) -> t.Dict[str, t.Any]:
7373
r"""Load data from file path directly to dictionary.
7474
7575
**YAML file**
@@ -114,7 +114,7 @@ def _from_file(cls, path: pathlib.Path):
114114
)
115115

116116
@classmethod
117-
def from_file(cls, path: pathlib.Path):
117+
def from_file(cls, path: pathlib.Path) -> "ConfigReader":
118118
r"""Load data from file path
119119
120120
**YAML file**

src/tmuxp/exc.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
~~~~~~~~~
55
66
"""
7+
import typing as t
8+
79
from ._compat import implements_to_string
810

911

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

2931

3032
class BeforeLoadScriptNotExists(OSError):
31-
def __init__(self, *args, **kwargs):
33+
def __init__(self, *args, **kwargs) -> None:
3234
super().__init__(*args, **kwargs)
3335

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

44-
def __init__(self, returncode, cmd, output=None):
46+
def __init__(
47+
self, returncode: int, cmd: str, output: t.Optional[str] = None
48+
) -> None:
4549
self.returncode = returncode
4650
self.cmd = cmd
4751
self.output = output

src/tmuxp/log.py

+64-60
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
}
3030

3131

32-
def setup_logger(logger=None, level="INFO"):
32+
def setup_logger(
33+
logger: t.Optional[logging.Logger] = None, level: str = "INFO"
34+
) -> None:
3335
"""
3436
Setup logging for CLI use.
3537
@@ -49,80 +51,82 @@ def setup_logger(logger=None, level="INFO"):
4951

5052

5153
def set_style(
52-
message, stylized, style_before=None, style_after=None, prefix="", suffix=""
53-
):
54+
message: str,
55+
stylized: bool,
56+
style_before: str = "",
57+
style_after: str = "",
58+
prefix: str = "",
59+
suffix: str = "",
60+
) -> str:
5461
if stylized:
5562
return prefix + style_before + message + style_after + suffix
5663

5764
return prefix + message + suffix
5865

5966

60-
def default_log_template(
61-
self: t.Type[logging.Formatter],
62-
record: logging.LogRecord,
63-
stylized: t.Optional[bool] = False,
64-
**kwargs: t.Any,
65-
) -> str:
66-
"""
67-
Return the prefix for the log message. Template for Formatter.
68-
69-
Parameters
70-
----------
71-
:py:class:`logging.LogRecord` :
72-
object. this is passed in from inside the
73-
:py:meth:`logging.Formatter.format` record.
74-
75-
Returns
76-
-------
77-
str
78-
template for logger message
79-
"""
80-
81-
reset = Style.RESET_ALL
82-
levelname = set_style(
83-
"(%(levelname)s)",
84-
stylized,
85-
style_before=(LEVEL_COLORS.get(record.levelname, "") + Style.BRIGHT),
86-
style_after=Style.RESET_ALL,
87-
suffix=" ",
88-
)
89-
asctime = set_style(
90-
"%(asctime)s",
91-
stylized,
92-
style_before=(Fore.BLACK + Style.DIM + Style.BRIGHT),
93-
style_after=(Fore.RESET + Style.RESET_ALL),
94-
prefix="[",
95-
suffix="]",
96-
)
97-
name = set_style(
98-
"%(name)s",
99-
stylized,
100-
style_before=(Fore.WHITE + Style.DIM + Style.BRIGHT),
101-
style_after=(Fore.RESET + Style.RESET_ALL),
102-
prefix=" ",
103-
suffix=" ",
104-
)
105-
106-
if stylized:
107-
return reset + levelname + asctime + name + reset
108-
109-
return levelname + asctime + name
110-
111-
11267
class LogFormatter(logging.Formatter):
113-
template = default_log_template
114-
115-
def __init__(self, color=True, *args, **kwargs):
68+
def template(
69+
self: logging.Formatter,
70+
record: logging.LogRecord,
71+
stylized: bool = False,
72+
**kwargs: t.Any,
73+
) -> str:
74+
"""
75+
Return the prefix for the log message. Template for Formatter.
76+
77+
Parameters
78+
----------
79+
:py:class:`logging.LogRecord` :
80+
object. this is passed in from inside the
81+
:py:meth:`logging.Formatter.format` record.
82+
83+
Returns
84+
-------
85+
str
86+
template for logger message
87+
"""
88+
reset = Style.RESET_ALL
89+
levelname = set_style(
90+
"(%(levelname)s)",
91+
stylized,
92+
style_before=(LEVEL_COLORS.get(record.levelname, "") + Style.BRIGHT),
93+
style_after=Style.RESET_ALL,
94+
suffix=" ",
95+
)
96+
asctime = set_style(
97+
"%(asctime)s",
98+
stylized,
99+
style_before=(Fore.BLACK + Style.DIM + Style.BRIGHT),
100+
style_after=(Fore.RESET + Style.RESET_ALL),
101+
prefix="[",
102+
suffix="]",
103+
)
104+
name = set_style(
105+
"%(name)s",
106+
stylized,
107+
style_before=(Fore.WHITE + Style.DIM + Style.BRIGHT),
108+
style_after=(Fore.RESET + Style.RESET_ALL),
109+
prefix=" ",
110+
suffix=" ",
111+
)
112+
113+
if stylized:
114+
return reset + levelname + asctime + name + reset
115+
116+
return levelname + asctime + name
117+
118+
def __init__(self, color: bool = True, *args, **kwargs) -> None:
116119
logging.Formatter.__init__(self, *args, **kwargs)
117120

118-
def format(self, record):
121+
def format(self, record: logging.LogRecord) -> str:
119122
try:
120123
record.message = record.getMessage()
121124
except Exception as e:
122125
record.message = f"Bad message ({e!r}): {record.__dict__!r}"
123126

124127
date_format = "%H:%m:%S"
125-
record.asctime = time.strftime(date_format, self.converter(record.created))
128+
formatting = self.converter(record.created) # type:ignore
129+
record.asctime = time.strftime(date_format, formatting)
126130

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

0 commit comments

Comments
 (0)