Skip to content

Python 3.9 Modernization #953

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 4 commits into from
Dec 21, 2024
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
10 changes: 10 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force

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

### Development

- Aggressive automated lint fixes via `ruff` (#953)

via ruff v0.8.4, all automated lint fixes, including unsafe and previews were applied for Python 3.9:

```sh
ruff check --select ALL . --fix --unsafe-fixes --preview --show-fixes; ruff format .
```

## tmuxp 1.49.0 (2024-11-26)

_Maintenance only, no bug fixes or new features_
Expand Down
4 changes: 2 additions & 2 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def monkeypatch_plugin_test_packages(monkeypatch: pytest.MonkeyPatch) -> None:


@pytest.fixture
def session_params(session_params: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
def session_params(session_params: dict[str, t.Any]) -> dict[str, t.Any]:
"""Terminal-friendly tmuxp session_params for dimensions."""
session_params.update({"x": 800, "y": 600})
return session_params
Expand All @@ -99,7 +99,7 @@ def socket_name(request: pytest.FixtureRequest) -> str:
@pytest.fixture(autouse=True)
def add_doctest_fixtures(
request: pytest.FixtureRequest,
doctest_namespace: t.Dict[str, t.Any],
doctest_namespace: dict[str, t.Any],
tmp_path: pathlib.Path,
) -> None:
"""Harness pytest fixtures to doctests namespace."""
Expand Down
26 changes: 13 additions & 13 deletions docs/_ext/aafig.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@


def merge_dict(
dst: t.Dict[str, t.Optional[str]],
src: t.Dict[str, t.Optional[str]],
) -> t.Dict[str, t.Optional[str]]:
dst: dict[str, t.Optional[str]],
src: dict[str, t.Optional[str]],
) -> dict[str, t.Optional[str]]:
for k, v in src.items():
if k not in dst:
dst[k] = v
Expand All @@ -51,15 +51,15 @@ def merge_dict(

def get_basename(
text: str,
options: t.Dict[str, str],
options: dict[str, str],
prefix: t.Optional[str] = "aafig",
) -> str:
options = options.copy()
if "format" in options:
del options["format"]
hashkey = text + str(options)
_id = sha(hashkey.encode("utf-8")).hexdigest()
return f"{prefix}-{_id}"
id_ = sha(hashkey.encode("utf-8")).hexdigest()
return f"{prefix}-{id_}"


class AafigError(SphinxError):
Expand All @@ -83,7 +83,7 @@ class AafigDirective(images.Image): # type:ignore
option_spec = images.Image.option_spec.copy()
option_spec.update(own_option_spec)

def run(self) -> t.List[nodes.Node]:
def run(self) -> list[nodes.Node]:
aafig_options = {}
own_options_keys = [self.own_option_spec.keys(), "scale"]
for k, v in self.options.items():
Expand Down Expand Up @@ -120,13 +120,13 @@ def render_aafig_images(app: "Sphinx", doctree: nodes.Node) -> None:
continue
options = img.aafig["options"]
text = img.aafig["text"]
_format = app.builder.format
format_ = app.builder.format
merge_dict(options, app.builder.config.aafig_default_options)
if _format in format_map:
options["format"] = format_map[_format]
if format_ in format_map:
options["format"] = format_map[format_]
else:
logger.warning(
f'unsupported builder format "{_format}", please '
f'unsupported builder format "{format_}", please '
"add a custom entry in aafig_format config "
"option for this builder",
)
Expand Down Expand Up @@ -159,8 +159,8 @@ def __init__(self, *args: object, **kwargs: object) -> None:
def render_aafigure(
app: "Sphinx",
text: str,
options: t.Dict[str, str],
) -> t.Tuple[str, str, t.Optional[str], t.Optional[str]]:
options: dict[str, str],
) -> tuple[str, str, t.Optional[str], t.Optional[str]]:
"""Render an ASCII art figure into the requested format output file."""
if aafigure is None:
raise AafigureNotInstalled
Expand Down
8 changes: 4 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
sys.path.insert(0, str(cwd / "_ext"))

# package data
about: t.Dict[str, str] = {}
about: dict[str, str] = {}
with (src_root / "tmuxp" / "__about__.py").open() as fp:
exec(fp.read(), about)

Expand Down Expand Up @@ -73,8 +73,8 @@
html_static_path = ["_static"]
html_favicon = "_static/favicon.ico"
html_theme = "furo"
html_theme_path: t.List[str] = []
html_theme_options: t.Dict[str, t.Union[str, t.List[t.Dict[str, str]]]] = {
html_theme_path: list[str] = []
html_theme_options: dict[str, t.Union[str, list[dict[str, str]]]] = {
"light_logo": "img/tmuxp.svg",
"dark_logo": "img/tmuxp.svg",
"footer_icons": [
Expand Down Expand Up @@ -143,7 +143,7 @@
}


def linkcode_resolve(domain: str, info: t.Dict[str, str]) -> t.Union[None, str]:
def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]:
"""
Determine the URL corresponding to Python object.

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ exclude_lines = [

[tool.mypy]
strict = true
python_version = "3.9"
files = [
"src/",
"tests/",
Expand All @@ -171,7 +172,7 @@ module = [
ignore_missing_imports = true

[tool.ruff]
target-version = "py38"
target-version = "py39"

[tool.ruff.lint]
select = [
Expand Down
10 changes: 5 additions & 5 deletions src/tmuxp/_internal/config_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

FormatLiteral = t.Literal["json", "yaml"]

RawConfigData: TypeAlias = t.Dict[t.Any, t.Any]
RawConfigData: TypeAlias = dict[t.Any, t.Any]


class ConfigReader:
Expand All @@ -28,7 +28,7 @@ def __init__(self, content: "RawConfigData") -> None:
self.content = content

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

>>> ConfigReader._load("json", '{ "session_name": "my session" }')
Expand All @@ -39,14 +39,14 @@ def _load(fmt: "FormatLiteral", content: str) -> t.Dict[str, t.Any]:
"""
if fmt == "yaml":
return t.cast(
t.Dict[str, t.Any],
"dict[str, t.Any]",
yaml.load(
content,
Loader=yaml.SafeLoader,
),
)
if fmt == "json":
return t.cast(t.Dict[str, t.Any], json.loads(content))
return t.cast("dict[str, t.Any]", json.loads(content))
msg = f"{fmt} not supported in configuration"
raise NotImplementedError(msg)

Expand Down Expand Up @@ -74,7 +74,7 @@ def load(cls, fmt: "FormatLiteral", content: str) -> "ConfigReader":
)

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

**YAML file**
Expand Down
8 changes: 3 additions & 5 deletions src/tmuxp/_internal/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,17 @@
...
"""

import typing as t

from typing_extensions import NotRequired, TypedDict


class PluginConfigSchema(TypedDict):
plugin_name: NotRequired[str]
tmux_min_version: NotRequired[str]
tmux_max_version: NotRequired[str]
tmux_version_incompatible: NotRequired[t.List[str]]
tmux_version_incompatible: NotRequired[list[str]]
libtmux_min_version: NotRequired[str]
libtmux_max_version: NotRequired[str]
libtmux_version_incompatible: NotRequired[t.List[str]]
libtmux_version_incompatible: NotRequired[list[str]]
tmuxp_min_version: NotRequired[str]
tmuxp_max_version: NotRequired[str]
tmuxp_version_incompatible: NotRequired[t.List[str]]
tmuxp_version_incompatible: NotRequired[list[str]]
2 changes: 1 addition & 1 deletion src/tmuxp/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class CLINamespace(argparse.Namespace):
ns = CLINamespace()


def cli(_args: t.Optional[t.List[str]] = None) -> None:
def cli(_args: t.Optional[list[str]] = None) -> None:
"""Manage tmux sessions.

Pass the "--help" argument to any command to see detailed help.
Expand Down
2 changes: 1 addition & 1 deletion src/tmuxp/cli/debug_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def command_debug_info(
) -> None:
"""Entrypoint for ``tmuxp debug-info`` to print debug info to submit with issues."""

def prepend_tab(strings: t.List[str]) -> t.List[str]:
def prepend_tab(strings: list[str]) -> list[str]:
"""Prepend tab to strings in list."""
return [f"\t{x}" for x in strings]

Expand Down
10 changes: 5 additions & 5 deletions src/tmuxp/cli/freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def command_freeze(
dest = os.path.abspath(os.path.relpath(os.path.expanduser(dest)))
workspace_format = args.workspace_format

valid_workspace_formats: t.List[CLIOutputFormatLiteral] = ["json", "yaml"]
valid_workspace_formats: list[CLIOutputFormatLiteral] = ["json", "yaml"]

def is_valid_ext(stem: t.Optional[str]) -> "TypeGuard[CLIOutputFormatLiteral]":
return stem in valid_workspace_formats
Expand All @@ -184,15 +184,15 @@ def extract_workspace_format(

workspace_format = extract_workspace_format(dest)
if not is_valid_ext(workspace_format):
_workspace_format = prompt_choices(
workspace_format_ = prompt_choices(
"Couldn't ascertain one of [{}] from file name. Convert to".format(
", ".join(valid_workspace_formats),
),
choices=t.cast(t.List[str], valid_workspace_formats),
choices=t.cast("list[str]", valid_workspace_formats),
default="yaml",
)
assert is_valid_ext(_workspace_format)
workspace_format = _workspace_format
assert is_valid_ext(workspace_format_)
workspace_format = workspace_format_

if workspace_format == "yaml":
workspace = configparser.dump(
Expand Down
2 changes: 1 addition & 1 deletion src/tmuxp/cli/import_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def create_import_subparser(
class ImportConfigFn(t.Protocol):
"""Typing for import configuration callback function."""

def __call__(self, workspace_dict: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
def __call__(self, workspace_dict: dict[str, t.Any]) -> dict[str, t.Any]:
"""Execute tmuxp import function."""
...

Expand Down
4 changes: 2 additions & 2 deletions src/tmuxp/cli/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class OptionOverrides(TypedDict):
class CLILoadNamespace(argparse.Namespace):
"""Typed :class:`argparse.Namespace` for tmuxp load command."""

workspace_files: t.List[str]
workspace_files: list[str]
socket_name: t.Optional[str]
socket_path: t.Optional[str]
tmux_config_file: t.Optional[str]
Expand All @@ -47,7 +47,7 @@ class CLILoadNamespace(argparse.Namespace):
log_file: t.Optional[str]


def load_plugins(session_config: t.Dict[str, t.Any]) -> t.List[t.Any]:
def load_plugins(session_config: dict[str, t.Any]) -> list[t.Any]:
"""Load and return plugins in workspace."""
plugins = []
if "plugins" in session_config:
Expand Down
26 changes: 13 additions & 13 deletions src/tmuxp/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
if t.TYPE_CHECKING:
from typing_extensions import TypeAlias

CLIColour: TypeAlias = t.Union[int, t.Tuple[int, int, int], str]
CLIColour: TypeAlias = t.Union[int, tuple[int, int, int], str]


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -56,10 +56,10 @@ def prompt(
`flask-script <https://github.com/techniq/flask-script>`_. See the
`flask-script license <https://github.com/techniq/flask-script/blob/master/LICENSE>`_.
"""
_prompt = name + ((default and f" [{default}]") or "")
_prompt += (name.endswith("?") and " ") or ": "
prompt_ = name + ((default and f" [{default}]") or "")
prompt_ += (name.endswith("?") and " ") or ": "
while True:
rv = input(_prompt) or default
rv = input(prompt_) or default
try:
if value_proc is not None and callable(value_proc):
assert isinstance(rv, str)
Expand Down Expand Up @@ -106,11 +106,11 @@ def prompt_bool(
else:
prompt_choice = "y/N"

_prompt = name + f" [{prompt_choice}]"
_prompt += (name.endswith("?") and " ") or ": "
prompt_ = name + f" [{prompt_choice}]"
prompt_ += (name.endswith("?") and " ") or ": "

while True:
rv = input(_prompt)
rv = input(prompt_)
if not rv:
return default
if rv.lower() in yes_choices:
Expand All @@ -126,7 +126,7 @@ def prompt_yes_no(name: str, default: bool = True) -> bool:

def prompt_choices(
name: str,
choices: t.Union[t.List[str], t.Tuple[str, str]],
choices: t.Union[list[str], tuple[str, str]],
default: t.Optional[str] = None,
no_choice: t.Sequence[str] = ("none",),
) -> t.Optional[str]:
Expand All @@ -148,16 +148,16 @@ def prompt_choices(
-------
str
"""
_choices: t.List[str] = []
options: t.List[str] = []
choices_: list[str] = []
options: list[str] = []

for choice in choices:
if isinstance(choice, str):
options.append(choice)
elif isinstance(choice, tuple):
options.append(f"{choice} [{choice[0]}]")
choice = choice[0]
_choices.append(choice)
choices_.append(choice)

while True:
rv = prompt(name + " - ({})".format(", ".join(options)), default=default)
Expand All @@ -166,7 +166,7 @@ def prompt_choices(
rv = rv.lower()
if rv in no_choice:
return None
if rv in _choices:
if rv in choices_:
return rv


Expand Down Expand Up @@ -201,7 +201,7 @@ def strip_ansi(value: str) -> str:


def _interpret_color(
color: t.Union[int, t.Tuple[int, int, int], str],
color: t.Union[int, tuple[int, int, int], str],
offset: int = 0,
) -> str:
if isinstance(color, int):
Expand Down
2 changes: 1 addition & 1 deletion src/tmuxp/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def format(self, record: logging.LogRecord) -> str:


def debug_log_template(
self: t.Type[logging.Formatter],
self: type[logging.Formatter],
record: logging.LogRecord,
stylized: t.Optional[bool] = False,
**kwargs: t.Any,
Expand Down
Loading
Loading