diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0bb06444e..44c1a4ce6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -69,7 +69,7 @@ jobs: export PATH=$HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin:$PATH ls $HOME/tmux-builds/tmux-${{ matrix.tmux-version }}/bin tmux -V - uv run py.test --cov=./ --cov-append --cov-report=xml -n auto + uv run py.test --cov=./ --cov-append --cov-report=xml -n auto --verbose env: COV_CORE_SOURCE: . COV_CORE_CONFIG: .coveragerc diff --git a/.tool-versions b/.tool-versions index 7987ea1eb..afca0980a 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -uv 0.5.11 +uv 0.5.22 python 3.13.1 3.12.8 3.11.11 3.10.16 3.9.21 3.8.20 3.7.17 diff --git a/CHANGES b/CHANGES index 9d31f0256..31565cee6 100644 --- a/CHANGES +++ b/CHANGES @@ -9,12 +9,35 @@ To install via [pip](https://pip.pypa.io/en/stable/), use: $ pip install --user --upgrade --pre libtmux ``` -## libtmux 0.41.x (Yet to be released) +## libtmux 0.42.x (Yet to be released) - _Future release notes will be placed here_ +## libtmux 0.41.0 (2025-02-02) + +### Fixes + +- {meth}`Server.__repr__()`: Use {meth}`os.geteuid()` for default `socket_path`. Thank you @lazysegtree! + (#557, resolves #556) + +### Documentation + +- `Server`: Fix `colors` docstring to note it accepts `88` or `256`, Thank you + @TravisDart! (via #544) + +### Development + +#### chore: Implement PEP 563 deferred annotation resolution (#555) + +- Add `from __future__ import annotations` to defer annotation resolution and reduce unnecessary runtime computations during type checking. +- Enable Ruff checks for PEP-compliant annotations: + - [non-pep585-annotation (UP006)](https://docs.astral.sh/ruff/rules/non-pep585-annotation/) + - [non-pep604-annotation (UP007)](https://docs.astral.sh/ruff/rules/non-pep604-annotation/) + +For more details on PEP 563, see: https://peps.python.org/pep-0563/ + ## libtmux 0.40.1 (2024-12-24) ### Bug fix diff --git a/conftest.py b/conftest.py index 0a5fee8f9..fe1c58050 100644 --- a/conftest.py +++ b/conftest.py @@ -8,7 +8,8 @@ https://docs.pytest.org/en/stable/deprecations.html """ -import pathlib +from __future__ import annotations + import shutil import typing as t @@ -21,6 +22,9 @@ from libtmux.session import Session from libtmux.window import Window +if t.TYPE_CHECKING: + import pathlib + pytest_plugins = ["pytester"] diff --git a/docs/about.md b/docs/about.md index 387186547..0f5557c81 100644 --- a/docs/about.md +++ b/docs/about.md @@ -95,7 +95,7 @@ have dashes (-) replaced with underscores (\_). ## Reference -- tmux docs +- tmux docs - tmux source code [abstraction layer]: http://en.wikipedia.org/wiki/Abstraction_layer diff --git a/docs/reference/common.md b/docs/api/common.md similarity index 100% rename from docs/reference/common.md rename to docs/api/common.md diff --git a/docs/reference/constants.md b/docs/api/constants.md similarity index 100% rename from docs/reference/constants.md rename to docs/api/constants.md diff --git a/docs/reference/exceptions.md b/docs/api/exceptions.md similarity index 100% rename from docs/reference/exceptions.md rename to docs/api/exceptions.md diff --git a/docs/reference/index.md b/docs/api/index.md similarity index 100% rename from docs/reference/index.md rename to docs/api/index.md diff --git a/docs/reference/panes.md b/docs/api/panes.md similarity index 100% rename from docs/reference/panes.md rename to docs/api/panes.md diff --git a/docs/reference/properties.md b/docs/api/properties.md similarity index 100% rename from docs/reference/properties.md rename to docs/api/properties.md diff --git a/docs/reference/servers.md b/docs/api/servers.md similarity index 100% rename from docs/reference/servers.md rename to docs/api/servers.md diff --git a/docs/reference/sessions.md b/docs/api/sessions.md similarity index 100% rename from docs/reference/sessions.md rename to docs/api/sessions.md diff --git a/docs/reference/windows.md b/docs/api/windows.md similarity index 100% rename from docs/reference/windows.md rename to docs/api/windows.md diff --git a/docs/conf.py b/docs/conf.py index dcdffada2..2297a3898 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,8 @@ # flake8: NOQA: E501 """Sphinx configuration for libtmux.""" +from __future__ import annotations + import contextlib import inspect import pathlib @@ -72,7 +74,7 @@ html_extra_path = ["manifest.json"] html_theme = "furo" html_theme_path: list[str] = [] -html_theme_options: dict[str, t.Union[str, list[dict[str, str]]]] = { +html_theme_options: dict[str, str | list[dict[str, str]]] = { "light_logo": "img/libtmux.svg", "dark_logo": "img/libtmux.svg", "footer_icons": [ @@ -90,7 +92,7 @@ "source_repository": f"{about['__github__']}/", "source_branch": "master", "source_directory": "docs/", - "announcement": "Friendly reminder: 📌 Pin the package, libtmux is pre-1.0 and APIs will be changing throughout 2022-2024.", + "announcement": "Friendly reminder: 📌 Pin the package, libtmux is pre-1.0 and APIs will be changing throughout 2025.", } html_sidebars = { "**": [ @@ -104,7 +106,7 @@ } # linkify_issues -issue_url_tpl = f'{about["__github__"]}/issues/{{issue_id}}' +issue_url_tpl = f"{about['__github__']}/issues/{{issue_id}}" # sphinx.ext.autodoc autoclass_content = "both" @@ -138,7 +140,7 @@ } -def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]: +def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str: """ Determine the URL corresponding to Python object. @@ -208,7 +210,7 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]: ) -def remove_tabs_js(app: "Sphinx", exc: Exception) -> None: +def remove_tabs_js(app: Sphinx, exc: Exception) -> None: """Remove tabs.js from _static after build.""" # Fix for sphinx-inline-tabs#18 if app.builder.format == "html" and not exc: @@ -217,6 +219,6 @@ def remove_tabs_js(app: "Sphinx", exc: Exception) -> None: tabs_js.unlink() # When python 3.7 deprecated, use missing_ok=True -def setup(app: "Sphinx") -> None: +def setup(app: Sphinx) -> None: """Configure Sphinx app hooks.""" app.connect("build-finished", remove_tabs_js) diff --git a/docs/index.md b/docs/index.md index a43882111..25da79752 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,7 +18,7 @@ hide-toc: true quickstart about topics/traversal -reference/index +api/index pytest-plugin/index ``` diff --git a/docs/redirects.txt b/docs/redirects.txt index 24daa931f..1f20db7c0 100644 --- a/docs/redirects.txt +++ b/docs/redirects.txt @@ -6,3 +6,12 @@ "sessions.md" "reference/sessions.md" "api.md" "reference/index.md" "pytest-plugin.md" "pytest-plugin/index.md" +"reference/common.md" "api/common.md" +"reference/constants.md" "api/constants.md" +"reference/exceptions.md" "api/exceptions.md" +"reference/index.md" "api/index.md" +"reference/panes.md" "api/panes.md" +"reference/properties.md" "api/properties.md" +"reference/servers.md" "api/servers.md" +"reference/sessions.md" "api/sessions.md" +"reference/windows.md" "api/windows.md" diff --git a/pyproject.toml b/pyproject.toml index 3d402c2e1..071506044 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "libtmux" -version = "0.40.1" +version = "0.41.0" description = "Typed library that provides an ORM wrapper for tmux, a terminal multiplexer." requires-python = ">=3.9,<4.0" authors = [ @@ -145,6 +145,7 @@ exclude_lines = [ "if TYPE_CHECKING:", "if t.TYPE_CHECKING:", "@overload( |$)", + "from __future__ import annotations", ] [tool.ruff] @@ -168,16 +169,31 @@ select = [ "PERF", # Perflint "RUF", # Ruff-specific rules "D", # pydocstyle + "FA100", # future annotations ] ignore = [ "COM812", # missing trailing comma, ruff format conflict ] +extend-safe-fixes = [ + "UP006", + "UP007", +] +pyupgrade.keep-runtime-typing = false [tool.ruff.lint.isort] known-first-party = [ "libtmux", ] combine-as-imports = true +required-imports = [ + "from __future__ import annotations", +] + +[tool.ruff.lint.flake8-builtins] +builtins-allowed-modules = [ + "dataclasses", + "types", +] [tool.ruff.lint.pydocstyle] convention = "numpy" diff --git a/src/libtmux/__about__.py b/src/libtmux/__about__.py index 1c1ca395a..9dca4560f 100644 --- a/src/libtmux/__about__.py +++ b/src/libtmux/__about__.py @@ -1,8 +1,10 @@ """Metadata package for libtmux.""" +from __future__ import annotations + __title__ = "libtmux" __package_name__ = "libtmux" -__version__ = "0.40.1" +__version__ = "0.41.0" __description__ = "Typed scripting library / ORM / API wrapper for tmux" __email__ = "tony@git-pull.com" __author__ = "Tony Narlock" diff --git a/src/libtmux/__init__.py b/src/libtmux/__init__.py index d63fad198..9e3bb7693 100644 --- a/src/libtmux/__init__.py +++ b/src/libtmux/__init__.py @@ -1,5 +1,7 @@ """libtmux, a typed, pythonic API wrapper for the tmux terminal multiplexer.""" +from __future__ import annotations + from .__about__ import ( __author__, __copyright__, diff --git a/src/libtmux/_internal/dataclasses.py b/src/libtmux/_internal/dataclasses.py index add92f34a..19b4a2d5f 100644 --- a/src/libtmux/_internal/dataclasses.py +++ b/src/libtmux/_internal/dataclasses.py @@ -5,6 +5,8 @@ This is an internal API not covered by versioning policy. """ +from __future__ import annotations + import dataclasses import typing as t from operator import attrgetter @@ -78,7 +80,7 @@ class SkipDefaultFieldsReprMixin: ItemWithMixin(name=Test, unit_price=2.05) """ - def __repr__(self: "DataclassInstance") -> str: + def __repr__(self: DataclassInstance) -> str: """Omit default fields in object representation.""" nodef_f_vals = ( (f.name, attrgetter(f.name)(self)) diff --git a/src/libtmux/_internal/query_list.py b/src/libtmux/_internal/query_list.py index 79ef702d7..332968dbd 100644 --- a/src/libtmux/_internal/query_list.py +++ b/src/libtmux/_internal/query_list.py @@ -5,11 +5,13 @@ This is an internal API not covered by versioning policy. """ +from __future__ import annotations + import logging import re import traceback import typing as t -from collections.abc import Iterable, Mapping, Sequence +from collections.abc import Callable, Iterable, Mapping, Sequence logger = logging.getLogger(__name__) @@ -20,8 +22,8 @@ class LookupProtocol(t.Protocol): def __call__( self, - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: """Return callback for :class:`QueryList` filtering operators.""" ... @@ -41,9 +43,9 @@ class ObjectDoesNotExist(Exception): def keygetter( - obj: "Mapping[str, t.Any]", + obj: Mapping[str, t.Any], path: str, -) -> t.Union[None, t.Any, str, list[str], "Mapping[str, str]"]: +) -> None | t.Any | str | list[str] | Mapping[str, str]: """Fetch values in objects and keys, supported nested data. **With dictionaries**: @@ -112,10 +114,10 @@ def keygetter( def parse_lookup( - obj: "Mapping[str, t.Any]", + obj: Mapping[str, t.Any], path: str, lookup: str, -) -> t.Optional[t.Any]: +) -> t.Any | None: """Check if field lookup key, e.g. "my__path__contains" has comparator, return val. If comparator not used or value not found, return None. @@ -151,15 +153,15 @@ def parse_lookup( def lookup_exact( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: return rhs == data def lookup_iexact( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False @@ -168,8 +170,8 @@ def lookup_iexact( def lookup_contains( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, (str, Mapping, list)): return False @@ -178,8 +180,8 @@ def lookup_contains( def lookup_icontains( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, (str, Mapping, list)): return False @@ -193,8 +195,8 @@ def lookup_icontains( def lookup_startswith( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False @@ -203,8 +205,8 @@ def lookup_startswith( def lookup_istartswith( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False @@ -213,8 +215,8 @@ def lookup_istartswith( def lookup_endswith( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False @@ -223,8 +225,8 @@ def lookup_endswith( def lookup_iendswith( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if not isinstance(rhs, str) or not isinstance(data, str): return False @@ -232,8 +234,8 @@ def lookup_iendswith( def lookup_in( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if isinstance(rhs, list): return data in rhs @@ -254,8 +256,8 @@ def lookup_in( def lookup_nin( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if isinstance(rhs, list): return data not in rhs @@ -276,8 +278,8 @@ def lookup_nin( def lookup_regex( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if isinstance(data, (str, bytes, re.Pattern)) and isinstance(rhs, (str, bytes)): return bool(re.search(rhs, data)) @@ -285,15 +287,15 @@ def lookup_regex( def lookup_iregex( - data: t.Union[str, list[str], "Mapping[str, str]"], - rhs: t.Union[str, list[str], "Mapping[str, str]", "re.Pattern[str]"], + data: str | list[str] | Mapping[str, str], + rhs: str | list[str] | Mapping[str, str] | re.Pattern[str], ) -> bool: if isinstance(data, (str, bytes, re.Pattern)) and isinstance(rhs, (str, bytes)): return bool(re.search(rhs, data, re.IGNORECASE)) return False -LOOKUP_NAME_MAP: 'Mapping[str, "LookupProtocol"]' = { +LOOKUP_NAME_MAP: Mapping[str, LookupProtocol] = { "eq": lookup_exact, "exact": lookup_exact, "iexact": lookup_iexact, @@ -469,10 +471,10 @@ class QueryList(list[T], t.Generic[T]): [] """ - data: "Sequence[T]" - pk_key: t.Optional[str] + data: Sequence[T] + pk_key: str | None - def __init__(self, items: t.Optional["Iterable[T]"] = None) -> None: + def __init__(self, items: Iterable[T] | None = None) -> None: super().__init__(items if items is not None else []) def items(self) -> list[tuple[str, T]]: @@ -505,9 +507,9 @@ def __eq__( def filter( self, - matcher: t.Optional[t.Union[t.Callable[[T], bool], T]] = None, + matcher: Callable[[T], bool] | T | None = None, **kwargs: t.Any, - ) -> "QueryList[T]": + ) -> QueryList[T]: """Filter list of objects.""" def filter_lookup(obj: t.Any) -> bool: @@ -534,7 +536,7 @@ def filter_lookup(obj: t.Any) -> bool: filter_ = matcher elif matcher is not None: - def val_match(obj: t.Union[str, list[t.Any], T]) -> bool: + def val_match(obj: str | list[t.Any] | T) -> bool: if isinstance(matcher, list): return obj in matcher return bool(obj == matcher) @@ -547,10 +549,10 @@ def val_match(obj: t.Union[str, list[t.Any], T]) -> bool: def get( self, - matcher: t.Optional[t.Union[t.Callable[[T], bool], T]] = None, - default: t.Optional[t.Any] = no_arg, + matcher: Callable[[T], bool] | T | None = None, + default: t.Any | None = no_arg, **kwargs: t.Any, - ) -> t.Optional[T]: + ) -> T | None: """Retrieve one object. Raises :exc:`MultipleObjectsReturned` if multiple objects found. diff --git a/src/libtmux/_vendor/_structures.py b/src/libtmux/_vendor/_structures.py index 25c1775d8..c2fd421dd 100644 --- a/src/libtmux/_vendor/_structures.py +++ b/src/libtmux/_vendor/_structures.py @@ -2,6 +2,7 @@ # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. +from __future__ import annotations class InfinityType: @@ -26,7 +27,7 @@ def __gt__(self, other: object) -> bool: def __ge__(self, other: object) -> bool: return True - def __neg__(self: object) -> "NegativeInfinityType": + def __neg__(self: object) -> NegativeInfinityType: return NegativeInfinity diff --git a/src/libtmux/_vendor/version.py b/src/libtmux/_vendor/version.py index 72af139db..7342bb5d4 100644 --- a/src/libtmux/_vendor/version.py +++ b/src/libtmux/_vendor/version.py @@ -9,22 +9,25 @@ from packaging.version import parse, Version """ +from __future__ import annotations + import collections import itertools import re -from typing import Callable, Optional, SupportsInt, Union +import typing as t +from collections.abc import Callable from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType __all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"] -InfiniteTypes = Union[InfinityType, NegativeInfinityType] -PrePostDevType = Union[InfiniteTypes, tuple[str, int]] -SubLocalType = Union[InfiniteTypes, int, str] -LocalType = Union[ +InfiniteTypes = t.Union[InfinityType, NegativeInfinityType] +PrePostDevType = t.Union[InfiniteTypes, tuple[str, int]] +SubLocalType = t.Union[InfiniteTypes, int, str] +LocalType = t.Union[ NegativeInfinityType, tuple[ - Union[ + t.Union[ SubLocalType, tuple[SubLocalType, str], tuple[NegativeInfinityType, SubLocalType], @@ -48,7 +51,7 @@ ) -def parse(version: str) -> "Version": +def parse(version: str) -> Version: """Parse the given version string. Examples @@ -91,13 +94,13 @@ def __hash__(self) -> int: # Please keep the duplicated `isinstance` check # in the six comparisons hereunder # unless you find a way to avoid adding overhead function calls. - def __lt__(self, other: "_BaseVersion") -> bool: + def __lt__(self, other: _BaseVersion) -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key < other._key - def __le__(self, other: "_BaseVersion") -> bool: + def __le__(self, other: _BaseVersion) -> bool: if not isinstance(other, _BaseVersion): return NotImplemented @@ -109,13 +112,13 @@ def __eq__(self, other: object) -> bool: return self._key == other._key - def __ge__(self, other: "_BaseVersion") -> bool: + def __ge__(self, other: _BaseVersion) -> bool: if not isinstance(other, _BaseVersion): return NotImplemented return self._key >= other._key - def __gt__(self, other: "_BaseVersion") -> bool: + def __gt__(self, other: _BaseVersion) -> bool: if not isinstance(other, _BaseVersion): return NotImplemented @@ -314,7 +317,7 @@ def release(self) -> tuple[int, ...]: return release @property - def pre(self) -> Optional[tuple[str, int]]: + def pre(self) -> tuple[str, int] | None: """The pre-release segment of the version. >>> print(Version("1.2.3").pre) @@ -326,11 +329,11 @@ def pre(self) -> Optional[tuple[str, int]]: >>> Version("1.2.3rc1").pre ('rc', 1) """ - pre: Optional[tuple[str, int]] = self._version.pre + pre: tuple[str, int] | None = self._version.pre return pre @property - def post(self) -> Optional[int]: + def post(self) -> int | None: """The post-release number of the version. >>> print(Version("1.2.3").post) @@ -341,7 +344,7 @@ def post(self) -> Optional[int]: return self._version.post[1] if self._version.post else None @property - def dev(self) -> Optional[int]: + def dev(self) -> int | None: """The development number of the version. >>> print(Version("1.2.3").dev) @@ -352,7 +355,7 @@ def dev(self) -> Optional[int]: return self._version.dev[1] if self._version.dev else None @property - def local(self) -> Optional[str]: + def local(self) -> str | None: """The local version segment of the version. >>> print(Version("1.2.3").local) @@ -475,8 +478,8 @@ def micro(self) -> int: def _parse_letter_version( letter: str, - number: Union[str, bytes, SupportsInt], -) -> Optional[tuple[str, int]]: + number: str | bytes | t.SupportsInt, +) -> tuple[str, int] | None: if letter: # We consider there to be an implicit 0 in a pre-release if there is # not a numeral associated with it. @@ -512,7 +515,7 @@ def _parse_letter_version( _local_version_separators = re.compile(r"[\._-]") -def _parse_local_version(local: str) -> Optional[LocalType]: +def _parse_local_version(local: str) -> LocalType | None: """Take a string like abc.1.twelve and turns it into ("abc", 1, "twelve").""" if local is not None: return tuple( @@ -525,10 +528,10 @@ def _parse_local_version(local: str) -> Optional[LocalType]: def _cmpkey( epoch: int, release: tuple[int, ...], - pre: Optional[tuple[str, int]], - post: Optional[tuple[str, int]], - dev: Optional[tuple[str, int]], - local: Optional[tuple[SubLocalType]], + pre: tuple[str, int] | None, + post: tuple[str, int] | None, + dev: tuple[str, int] | None, + local: tuple[SubLocalType] | None, ) -> CmpKey: # When we compare a release version, we want to compare it with all of the # trailing zeros removed. So we'll use a reverse the list, drop all the now diff --git a/src/libtmux/common.py b/src/libtmux/common.py index 62e82d12b..83bfb5cd2 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -5,17 +5,21 @@ """ +from __future__ import annotations + import logging import re import shutil import subprocess import sys import typing as t -from typing import Optional, Union from . import exc from ._compat import LooseVersion, console_to_str, str_from_console +if t.TYPE_CHECKING: + from collections.abc import Callable + logger = logging.getLogger(__name__) @@ -36,9 +40,9 @@ class EnvironmentMixin: _add_option = None - cmd: t.Callable[[t.Any, t.Any], "tmux_cmd"] + cmd: Callable[[t.Any, t.Any], tmux_cmd] - def __init__(self, add_option: Optional[str] = None) -> None: + def __init__(self, add_option: str | None = None) -> None: self._add_option = add_option def set_environment(self, name: str, value: str) -> None: @@ -116,7 +120,7 @@ def remove_environment(self, name: str) -> None: msg = f"tmux set-environment stderr: {cmd.stderr}" raise ValueError(msg) - def show_environment(self) -> dict[str, Union[bool, str]]: + def show_environment(self) -> dict[str, bool | str]: """Show environment ``$ tmux show-environment -t [session]``. Return dict of environment variables for the session. @@ -137,7 +141,7 @@ def show_environment(self) -> dict[str, Union[bool, str]]: cmd = self.cmd(*tmux_args) output = cmd.stdout opts = [tuple(item.split("=", 1)) for item in output] - opts_dict: dict[str, t.Union[str, bool]] = {} + opts_dict: dict[str, str | bool] = {} for _t in opts: if len(_t) == 2: opts_dict[_t[0]] = _t[1] @@ -148,7 +152,7 @@ def show_environment(self) -> dict[str, Union[bool, str]]: return opts_dict - def getenv(self, name: str) -> Optional[t.Union[str, bool]]: + def getenv(self, name: str) -> str | bool | None: """Show environment variable ``$ tmux show-environment -t [session] ``. Return the value of a specific variable if the name is specified. @@ -165,7 +169,7 @@ def getenv(self, name: str) -> Optional[t.Union[str, bool]]: str Value of environment variable """ - tmux_args: tuple[t.Union[str, int], ...] = () + tmux_args: tuple[str | int, ...] = () tmux_args += ("show-environment",) if self._add_option: @@ -174,7 +178,7 @@ def getenv(self, name: str) -> Optional[t.Union[str, bool]]: cmd = self.cmd(*tmux_args) output = cmd.stdout opts = [tuple(item.split("=", 1)) for item in output] - opts_dict: dict[str, t.Union[str, bool]] = {} + opts_dict: dict[str, str | bool] = {} for _t in opts: if len(_t) == 2: opts_dict[_t[0]] = _t[1] @@ -422,7 +426,7 @@ def has_minimum_version(raises: bool = True) -> bool: return True -def session_check_name(session_name: t.Optional[str]) -> None: +def session_check_name(session_name: str | None) -> None: """Raise exception session name invalid, modeled after tmux function. tmux(1) session names may not be empty, or include periods or colons. diff --git a/src/libtmux/constants.py b/src/libtmux/constants.py index 41698a8f0..b4c23ee64 100644 --- a/src/libtmux/constants.py +++ b/src/libtmux/constants.py @@ -1,5 +1,7 @@ """Constant variables for libtmux.""" +from __future__ import annotations + import enum diff --git a/src/libtmux/exc.py b/src/libtmux/exc.py index 2c665c335..7777403f3 100644 --- a/src/libtmux/exc.py +++ b/src/libtmux/exc.py @@ -5,6 +5,8 @@ """ +from __future__ import annotations + import typing as t from libtmux._internal.query_list import ObjectDoesNotExist @@ -30,16 +32,16 @@ class TmuxObjectDoesNotExist(ObjectDoesNotExist): def __init__( self, - obj_key: t.Optional[str] = None, - obj_id: t.Optional[str] = None, - list_cmd: t.Optional[str] = None, - list_extra_args: "t.Optional[ListExtraArgs]" = None, + obj_key: str | None = None, + obj_id: str | None = None, + list_cmd: str | None = None, + list_extra_args: ListExtraArgs | None = None, *args: object, ) -> None: if all(arg is not None for arg in [obj_key, obj_id, list_cmd, list_extra_args]): return super().__init__( f"Could not find {obj_key}={obj_id} for {list_cmd} " - f'{list_extra_args if list_extra_args is not None else ""}', + f"{list_extra_args if list_extra_args is not None else ''}", ) return super().__init__("Could not find object") @@ -54,7 +56,7 @@ class BadSessionName(LibTmuxException): def __init__( self, reason: str, - session_name: t.Optional[str] = None, + session_name: str | None = None, *args: object, ) -> None: msg = f"Bad session name: {reason}" @@ -93,7 +95,7 @@ class WaitTimeout(LibTmuxException): class VariableUnpackingError(LibTmuxException): """Error unpacking variable.""" - def __init__(self, variable: t.Optional[t.Any] = None, *args: object) -> None: + def __init__(self, variable: t.Any | None = None, *args: object) -> None: return super().__init__(f"Unexpected variable: {variable!s}") @@ -104,7 +106,7 @@ class PaneError(LibTmuxException): class PaneNotFound(PaneError): """Pane not found.""" - def __init__(self, pane_id: t.Optional[str] = None, *args: object) -> None: + def __init__(self, pane_id: str | None = None, *args: object) -> None: if pane_id is not None: return super().__init__(f"Pane not found: {pane_id}") return super().__init__("Pane not found") diff --git a/src/libtmux/formats.py b/src/libtmux/formats.py index e6e60c370..a14d10486 100644 --- a/src/libtmux/formats.py +++ b/src/libtmux/formats.py @@ -7,6 +7,8 @@ """ +from __future__ import annotations + import os FORMAT_SEPARATOR = os.environ.get("LIBTMUX_TMUX_FORMAT_SEPARATOR", "␞") diff --git a/src/libtmux/neo.py b/src/libtmux/neo.py index f644014e1..ab5cd712b 100644 --- a/src/libtmux/neo.py +++ b/src/libtmux/neo.py @@ -1,8 +1,11 @@ """Tools for hydrating tmux data into python dataclass objects.""" +from __future__ import annotations + import dataclasses import logging import typing as t +from collections.abc import Iterable from libtmux import exc from libtmux.common import tmux_cmd @@ -10,7 +13,7 @@ if t.TYPE_CHECKING: ListCmd = t.Literal["list-sessions", "list-windows", "list-panes"] - ListExtraArgs = t.Optional[t.Iterable[str]] + ListExtraArgs = t.Optional[Iterable[str]] from libtmux.server import Server @@ -35,143 +38,143 @@ class Obj: """Dataclass of generic tmux object.""" - server: "Server" + server: Server - active_window_index: t.Union[str, None] = None - alternate_saved_x: t.Union[str, None] = None - alternate_saved_y: t.Union[str, None] = None + active_window_index: str | None = None + alternate_saved_x: str | None = None + alternate_saved_y: str | None = None # See QUIRK_TMUX_3_1_X_0001 - buffer_name: t.Union[str, None] = None - buffer_sample: t.Union[str, None] = None - buffer_size: t.Union[str, None] = None + buffer_name: str | None = None + buffer_sample: str | None = None + buffer_size: str | None = None # See QUIRK_TMUX_3_1_X_0001 - client_cell_height: t.Union[str, None] = None - client_cell_width: t.Union[str, None] = None + client_cell_height: str | None = None + client_cell_width: str | None = None # See QUIRK_TMUX_3_1_X_0001 - client_discarded: t.Union[str, None] = None - client_flags: t.Union[str, None] = None - client_height: t.Union[str, None] = None - client_key_table: t.Union[str, None] = None - client_name: t.Union[str, None] = None - client_pid: t.Union[str, None] = None - client_termname: t.Union[str, None] = None - client_tty: t.Union[str, None] = None - client_uid: t.Union[str, None] = None - client_user: t.Union[str, None] = None - client_width: t.Union[str, None] = None - client_written: t.Union[str, None] = None - command_list_alias: t.Union[str, None] = None - command_list_name: t.Union[str, None] = None - command_list_usage: t.Union[str, None] = None - config_files: t.Union[str, None] = None - copy_cursor_line: t.Union[str, None] = None - copy_cursor_word: t.Union[str, None] = None - copy_cursor_x: t.Union[str, None] = None - copy_cursor_y: t.Union[str, None] = None - current_file: t.Union[str, None] = None - cursor_character: t.Union[str, None] = None - cursor_flag: t.Union[str, None] = None - cursor_x: t.Union[str, None] = None - cursor_y: t.Union[str, None] = None - history_bytes: t.Union[str, None] = None - history_limit: t.Union[str, None] = None - history_size: t.Union[str, None] = None - insert_flag: t.Union[str, None] = None - keypad_cursor_flag: t.Union[str, None] = None - keypad_flag: t.Union[str, None] = None - last_window_index: t.Union[str, None] = None - line: t.Union[str, None] = None - mouse_all_flag: t.Union[str, None] = None - mouse_any_flag: t.Union[str, None] = None - mouse_button_flag: t.Union[str, None] = None - mouse_sgr_flag: t.Union[str, None] = None - mouse_standard_flag: t.Union[str, None] = None - next_session_id: t.Union[str, None] = None - origin_flag: t.Union[str, None] = None - pane_active: t.Union[str, None] = None # Not detected by script - pane_at_bottom: t.Union[str, None] = None - pane_at_left: t.Union[str, None] = None - pane_at_right: t.Union[str, None] = None - pane_at_top: t.Union[str, None] = None - pane_bg: t.Union[str, None] = None - pane_bottom: t.Union[str, None] = None - pane_current_command: t.Union[str, None] = None - pane_current_path: t.Union[str, None] = None - pane_dead_signal: t.Union[str, None] = None - pane_dead_status: t.Union[str, None] = None - pane_dead_time: t.Union[str, None] = None - pane_fg: t.Union[str, None] = None - pane_height: t.Union[str, None] = None - pane_id: t.Union[str, None] = None - pane_index: t.Union[str, None] = None - pane_left: t.Union[str, None] = None - pane_pid: t.Union[str, None] = None - pane_right: t.Union[str, None] = None - pane_search_string: t.Union[str, None] = None - pane_start_command: t.Union[str, None] = None - pane_start_path: t.Union[str, None] = None - pane_tabs: t.Union[str, None] = None - pane_top: t.Union[str, None] = None - pane_tty: t.Union[str, None] = None - pane_width: t.Union[str, None] = None - pid: t.Union[str, None] = None - scroll_position: t.Union[str, None] = None - scroll_region_lower: t.Union[str, None] = None - scroll_region_upper: t.Union[str, None] = None - search_match: t.Union[str, None] = None - selection_end_x: t.Union[str, None] = None - selection_end_y: t.Union[str, None] = None - selection_start_x: t.Union[str, None] = None - selection_start_y: t.Union[str, None] = None - session_activity: t.Union[str, None] = None - session_alerts: t.Union[str, None] = None - session_attached: t.Union[str, None] = None - session_attached_list: t.Union[str, None] = None - session_created: t.Union[str, None] = None - session_group: t.Union[str, None] = None - session_group_attached: t.Union[str, None] = None - session_group_list: t.Union[str, None] = None - session_group_size: t.Union[str, None] = None - session_id: t.Union[str, None] = None - session_last_attached: t.Union[str, None] = None - session_name: t.Union[str, None] = None - session_path: t.Union[str, None] = None - session_stack: t.Union[str, None] = None - session_windows: t.Union[str, None] = None - socket_path: t.Union[str, None] = None - start_time: t.Union[str, None] = None - uid: t.Union[str, None] = None - user: t.Union[str, None] = None - version: t.Union[str, None] = None - window_active: t.Union[str, None] = None # Not detected by script - window_active_clients: t.Union[str, None] = None - window_active_sessions: t.Union[str, None] = None - window_activity: t.Union[str, None] = None - window_cell_height: t.Union[str, None] = None - window_cell_width: t.Union[str, None] = None - window_height: t.Union[str, None] = None - window_id: t.Union[str, None] = None - window_index: t.Union[str, None] = None - window_layout: t.Union[str, None] = None - window_linked: t.Union[str, None] = None - window_linked_sessions: t.Union[str, None] = None - window_linked_sessions_list: t.Union[str, None] = None - window_marked_flag: t.Union[str, None] = None - window_name: t.Union[str, None] = None - window_offset_x: t.Union[str, None] = None - window_offset_y: t.Union[str, None] = None - window_panes: t.Union[str, None] = None - window_raw_flags: t.Union[str, None] = None - window_stack_index: t.Union[str, None] = None - window_width: t.Union[str, None] = None - wrap_flag: t.Union[str, None] = None + client_discarded: str | None = None + client_flags: str | None = None + client_height: str | None = None + client_key_table: str | None = None + client_name: str | None = None + client_pid: str | None = None + client_termname: str | None = None + client_tty: str | None = None + client_uid: str | None = None + client_user: str | None = None + client_width: str | None = None + client_written: str | None = None + command_list_alias: str | None = None + command_list_name: str | None = None + command_list_usage: str | None = None + config_files: str | None = None + copy_cursor_line: str | None = None + copy_cursor_word: str | None = None + copy_cursor_x: str | None = None + copy_cursor_y: str | None = None + current_file: str | None = None + cursor_character: str | None = None + cursor_flag: str | None = None + cursor_x: str | None = None + cursor_y: str | None = None + history_bytes: str | None = None + history_limit: str | None = None + history_size: str | None = None + insert_flag: str | None = None + keypad_cursor_flag: str | None = None + keypad_flag: str | None = None + last_window_index: str | None = None + line: str | None = None + mouse_all_flag: str | None = None + mouse_any_flag: str | None = None + mouse_button_flag: str | None = None + mouse_sgr_flag: str | None = None + mouse_standard_flag: str | None = None + next_session_id: str | None = None + origin_flag: str | None = None + pane_active: str | None = None # Not detected by script + pane_at_bottom: str | None = None + pane_at_left: str | None = None + pane_at_right: str | None = None + pane_at_top: str | None = None + pane_bg: str | None = None + pane_bottom: str | None = None + pane_current_command: str | None = None + pane_current_path: str | None = None + pane_dead_signal: str | None = None + pane_dead_status: str | None = None + pane_dead_time: str | None = None + pane_fg: str | None = None + pane_height: str | None = None + pane_id: str | None = None + pane_index: str | None = None + pane_left: str | None = None + pane_pid: str | None = None + pane_right: str | None = None + pane_search_string: str | None = None + pane_start_command: str | None = None + pane_start_path: str | None = None + pane_tabs: str | None = None + pane_top: str | None = None + pane_tty: str | None = None + pane_width: str | None = None + pid: str | None = None + scroll_position: str | None = None + scroll_region_lower: str | None = None + scroll_region_upper: str | None = None + search_match: str | None = None + selection_end_x: str | None = None + selection_end_y: str | None = None + selection_start_x: str | None = None + selection_start_y: str | None = None + session_activity: str | None = None + session_alerts: str | None = None + session_attached: str | None = None + session_attached_list: str | None = None + session_created: str | None = None + session_group: str | None = None + session_group_attached: str | None = None + session_group_list: str | None = None + session_group_size: str | None = None + session_id: str | None = None + session_last_attached: str | None = None + session_name: str | None = None + session_path: str | None = None + session_stack: str | None = None + session_windows: str | None = None + socket_path: str | None = None + start_time: str | None = None + uid: str | None = None + user: str | None = None + version: str | None = None + window_active: str | None = None # Not detected by script + window_active_clients: str | None = None + window_active_sessions: str | None = None + window_activity: str | None = None + window_cell_height: str | None = None + window_cell_width: str | None = None + window_height: str | None = None + window_id: str | None = None + window_index: str | None = None + window_layout: str | None = None + window_linked: str | None = None + window_linked_sessions: str | None = None + window_linked_sessions_list: str | None = None + window_marked_flag: str | None = None + window_name: str | None = None + window_offset_x: str | None = None + window_offset_y: str | None = None + window_panes: str | None = None + window_raw_flags: str | None = None + window_stack_index: str | None = None + window_width: str | None = None + wrap_flag: str | None = None def _refresh( self, obj_key: str, obj_id: str, - list_cmd: "ListCmd" = "list-panes", - list_extra_args: "t.Optional[ListExtraArgs]" = None, + list_cmd: ListCmd = "list-panes", + list_extra_args: ListExtraArgs | None = None, ) -> None: assert isinstance(obj_id, str) obj = fetch_obj( @@ -188,14 +191,14 @@ def _refresh( def fetch_objs( - server: "Server", - list_cmd: "ListCmd", - list_extra_args: "t.Optional[ListExtraArgs]" = None, + server: Server, + list_cmd: ListCmd, + list_extra_args: ListExtraArgs | None = None, ) -> OutputsRaw: """Fetch a listing of raw data from a tmux command.""" formats = list(Obj.__dataclass_fields__.keys()) - cmd_args: list[t.Union[str, int]] = [] + cmd_args: list[str | int] = [] if server.socket_name: cmd_args.insert(0, f"-L{server.socket_name}") @@ -208,7 +211,7 @@ def fetch_objs( list_cmd, ] - if list_extra_args is not None and isinstance(list_extra_args, t.Iterable): + if list_extra_args is not None and isinstance(list_extra_args, Iterable): tmux_cmds.extend(list(list_extra_args)) tmux_cmds.append("-F{}".format("".join(tmux_formats))) @@ -230,11 +233,11 @@ def fetch_objs( def fetch_obj( - server: "Server", + server: Server, obj_key: str, obj_id: str, - list_cmd: "ListCmd" = "list-panes", - list_extra_args: "t.Optional[ListExtraArgs]" = None, + list_cmd: ListCmd = "list-panes", + list_extra_args: ListExtraArgs | None = None, ) -> OutputRaw: """Fetch raw data from tmux command.""" obj_formatters_filtered = fetch_objs( diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 9586e68e5..76208c61f 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -5,12 +5,13 @@ """ +from __future__ import annotations + import dataclasses import logging import pathlib import typing as t import warnings -from typing import overload from libtmux.common import has_gte_version, has_lt_version, tmux_cmd from libtmux.constants import ( @@ -74,7 +75,7 @@ class Pane(Obj): Accessed April 1st, 2018. """ - server: "Server" + server: Server def refresh(self) -> None: """Refresh pane attributes from tmux.""" @@ -86,7 +87,7 @@ def refresh(self) -> None: ) @classmethod - def from_pane_id(cls, server: "Server", pane_id: str) -> "Pane": + def from_pane_id(cls, server: Server, pane_id: str) -> Pane: """Create Pane from existing pane_id.""" pane = fetch_obj( obj_key="pane_id", @@ -101,7 +102,7 @@ def from_pane_id(cls, server: "Server", pane_id: str) -> "Pane": # Relations # @property - def window(self) -> "Window": + def window(self) -> Window: """Parent window of pane.""" assert isinstance(self.window_id, str) from libtmux.window import Window @@ -109,7 +110,7 @@ def window(self) -> "Window": return Window.from_window_id(server=self.server, window_id=self.window_id) @property - def session(self) -> "Session": + def session(self) -> Session: """Parent session of pane.""" return self.window.session @@ -121,7 +122,7 @@ def cmd( self, cmd: str, *args: t.Any, - target: t.Optional[t.Union[str, int]] = None, + target: str | int | None = None, ) -> tmux_cmd: """Execute tmux subcommand within pane context. @@ -161,18 +162,18 @@ def resize( self, /, # Adjustments - adjustment_direction: t.Optional[ResizeAdjustmentDirection] = None, - adjustment: t.Optional[int] = None, + adjustment_direction: ResizeAdjustmentDirection | None = None, + adjustment: int | None = None, # Manual - height: t.Optional[t.Union[str, int]] = None, - width: t.Optional[t.Union[str, int]] = None, + height: str | int | None = None, + width: str | int | None = None, # Zoom - zoom: t.Optional[bool] = None, + zoom: bool | None = None, # Mouse - mouse: t.Optional[bool] = None, + mouse: bool | None = None, # Optional flags - trim_below: t.Optional[bool] = None, - ) -> "Pane": + trim_below: bool | None = None, + ) -> Pane: """Resize tmux pane. Parameters @@ -260,9 +261,9 @@ def resize( def capture_pane( self, - start: t.Union["t.Literal['-']", t.Optional[int]] = None, - end: t.Union["t.Literal['-']", t.Optional[int]] = None, - ) -> t.Union[str, list[str]]: + start: t.Literal["-"] | int | None = None, + end: t.Literal["-"] | int | None = None, + ) -> str | list[str]: """Capture text from pane. ``$ tmux capture-pane`` to pane. @@ -297,9 +298,9 @@ def capture_pane( def send_keys( self, cmd: str, - enter: t.Optional[bool] = True, - suppress_history: t.Optional[bool] = False, - literal: t.Optional[bool] = False, + enter: bool | None = True, + suppress_history: bool | None = False, + literal: bool | None = False, ) -> None: r"""``$ tmux send-keys`` to the pane. @@ -347,21 +348,21 @@ def send_keys( if enter: self.enter() - @overload + @t.overload def display_message( self, cmd: str, - get_text: "t.Literal[True]", - ) -> t.Union[str, list[str]]: ... + get_text: t.Literal[True], + ) -> str | list[str]: ... - @overload - def display_message(self, cmd: str, get_text: "t.Literal[False]") -> None: ... + @t.overload + def display_message(self, cmd: str, get_text: t.Literal[False]) -> None: ... def display_message( self, cmd: str, get_text: bool = False, - ) -> t.Optional[t.Union[str, list[str]]]: + ) -> str | list[str] | None: """Display message to pane. Displays a message in target-client status line. @@ -382,7 +383,7 @@ def display_message( def kill( self, - all_except: t.Optional[bool] = None, + all_except: bool | None = None, ) -> None: """Kill :class:`Pane`. @@ -443,7 +444,7 @@ def kill( additional scoped window info. """ - def select(self) -> "Pane": + def select(self) -> Pane: """Select pane. Examples @@ -476,7 +477,7 @@ def select(self) -> "Pane": return self - def select_pane(self) -> "Pane": + def select_pane(self) -> Pane: """Select pane. Notes @@ -499,16 +500,16 @@ def select_pane(self) -> "Pane": def split( self, /, - target: t.Optional[t.Union[int, str]] = None, - start_directory: t.Optional[str] = None, + target: int | str | None = None, + start_directory: str | None = None, attach: bool = False, - direction: t.Optional[PaneDirection] = None, - full_window_split: t.Optional[bool] = None, - zoom: t.Optional[bool] = None, - shell: t.Optional[str] = None, - size: t.Optional[t.Union[str, int]] = None, - environment: t.Optional[dict[str, str]] = None, - ) -> "Pane": + direction: PaneDirection | None = None, + full_window_split: bool | None = None, + zoom: bool | None = None, + shell: str | None = None, + size: str | int | None = None, + environment: dict[str, str] | None = None, + ) -> Pane: """Split window and return :class:`Pane`, by default beneath current pane. Parameters @@ -603,7 +604,7 @@ def split( if size is not None: if has_lt_version("3.1"): if isinstance(size, str) and size.endswith("%"): - tmux_args += (f'-p{str(size).rstrip("%")}',) + tmux_args += (f"-p{str(size).rstrip('%')}",) else: warnings.warn( 'Ignored size. Use percent in tmux < 3.1, e.g. "size=50%"', @@ -663,7 +664,7 @@ def split( Commands (helpers) """ - def set_width(self, width: int) -> "Pane": + def set_width(self, width: int) -> Pane: """Set pane width. Parameters @@ -674,7 +675,7 @@ def set_width(self, width: int) -> "Pane": self.resize_pane(width=width) return self - def set_height(self, height: int) -> "Pane": + def set_height(self, height: int) -> Pane: """Set pane height. Parameters @@ -685,7 +686,7 @@ def set_height(self, height: int) -> "Pane": self.resize_pane(height=height) return self - def enter(self) -> "Pane": + def enter(self) -> Pane: """Send carriage return to pane. ``$ tmux send-keys`` send Enter to the pane. @@ -693,12 +694,12 @@ def enter(self) -> "Pane": self.cmd("send-keys", "Enter") return self - def clear(self) -> "Pane": + def clear(self) -> Pane: """Clear pane.""" self.send_keys("reset") return self - def reset(self) -> "Pane": + def reset(self) -> Pane: """Reset and clear pane history.""" self.cmd("send-keys", r"-R \; clear-history") return self @@ -720,7 +721,7 @@ def __repr__(self) -> str: # Aliases # @property - def id(self) -> t.Optional[str]: + def id(self) -> str | None: """Alias of :attr:`Pane.pane_id`. >>> pane.id @@ -732,7 +733,7 @@ def id(self) -> t.Optional[str]: return self.pane_id @property - def index(self) -> t.Optional[str]: + def index(self) -> str | None: """Alias of :attr:`Pane.pane_index`. >>> pane.index @@ -744,7 +745,7 @@ def index(self) -> t.Optional[str]: return self.pane_index @property - def height(self) -> t.Optional[str]: + def height(self) -> str | None: """Alias of :attr:`Pane.pane_height`. >>> pane.height.isdigit() @@ -756,7 +757,7 @@ def height(self) -> t.Optional[str]: return self.pane_height @property - def width(self) -> t.Optional[str]: + def width(self) -> str | None: """Alias of :attr:`Pane.pane_width`. >>> pane.width.isdigit() @@ -820,15 +821,15 @@ def at_right(self) -> bool: # def split_window( self, - target: t.Optional[t.Union[int, str]] = None, + target: int | str | None = None, attach: bool = False, - start_directory: t.Optional[str] = None, + start_directory: str | None = None, vertical: bool = True, - shell: t.Optional[str] = None, - size: t.Optional[t.Union[str, int]] = None, - percent: t.Optional[int] = None, # deprecated - environment: t.Optional[dict[str, str]] = None, - ) -> "Pane": # New Pane, not self + shell: str | None = None, + size: str | int | None = None, + percent: int | None = None, # deprecated + environment: dict[str, str] | None = None, + ) -> Pane: # New Pane, not self """Split window at pane and return newly created :class:`Pane`. Parameters @@ -856,7 +857,7 @@ def split_window( stacklevel=2, ) if size is None and percent is not None: - size = f'{str(percent).rstrip("%")}%' + size = f"{str(percent).rstrip('%')}%" return self.split( target=target, attach=attach, @@ -867,7 +868,7 @@ def split_window( environment=environment, ) - def get(self, key: str, default: t.Optional[t.Any] = None) -> t.Any: + def get(self, key: str, default: t.Any | None = None) -> t.Any: """Return key-based lookup. Deprecated by attributes. .. deprecated:: 0.16 @@ -902,18 +903,18 @@ def __getitem__(self, key: str) -> t.Any: def resize_pane( self, # Adjustments - adjustment_direction: t.Optional[ResizeAdjustmentDirection] = None, - adjustment: t.Optional[int] = None, + adjustment_direction: ResizeAdjustmentDirection | None = None, + adjustment: int | None = None, # Manual - height: t.Optional[t.Union[str, int]] = None, - width: t.Optional[t.Union[str, int]] = None, + height: str | int | None = None, + width: str | int | None = None, # Zoom - zoom: t.Optional[bool] = None, + zoom: bool | None = None, # Mouse - mouse: t.Optional[bool] = None, + mouse: bool | None = None, # Optional flags - trim_below: t.Optional[bool] = None, - ) -> "Pane": + trim_below: bool | None = None, + ) -> Pane: """Resize pane, deprecated by :meth:`Pane.resize`. .. deprecated:: 0.28 diff --git a/src/libtmux/pytest_plugin.py b/src/libtmux/pytest_plugin.py index 3dcca9963..aad746912 100644 --- a/src/libtmux/pytest_plugin.py +++ b/src/libtmux/pytest_plugin.py @@ -1,10 +1,11 @@ """libtmux pytest plugin.""" +from __future__ import annotations + import contextlib import getpass import logging import os -import pathlib import typing as t import pytest @@ -14,6 +15,8 @@ from libtmux.test import TEST_SESSION_PREFIX, get_test_session_name, namer if t.TYPE_CHECKING: + import pathlib + from libtmux.session import Session logger = logging.getLogger(__name__) @@ -189,7 +192,7 @@ def session( request: pytest.FixtureRequest, session_params: dict[str, t.Any], server: Server, -) -> "Session": +) -> Session: """Return new, temporary :class:`libtmux.Session`. >>> from libtmux.session import Session diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 89ea2b90b..d235eafed 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -5,6 +5,8 @@ """ +from __future__ import annotations + import logging import os import pathlib @@ -96,7 +98,7 @@ class Server(EnvironmentMixin): config_file = None """Passthrough to ``[-f file]``""" colors = None - """``-2`` or ``-8``""" + """``256`` or ``88``""" child_id_attribute = "session_id" """Unique child ID used by :class:`~libtmux.common.TmuxRelationalObject`""" formatter_prefix = "server_" @@ -104,10 +106,10 @@ class Server(EnvironmentMixin): def __init__( self, - socket_name: t.Optional[str] = None, - socket_path: t.Optional[t.Union[str, pathlib.Path]] = None, - config_file: t.Optional[str] = None, - colors: t.Optional[int] = None, + socket_name: str | None = None, + socket_path: str | pathlib.Path | None = None, + config_file: str | None = None, + colors: int | None = None, **kwargs: t.Any, ) -> None: EnvironmentMixin.__init__(self, "-g") @@ -178,7 +180,7 @@ def cmd( self, cmd: str, *args: t.Any, - target: t.Optional[t.Union[str, int]] = None, + target: str | int | None = None, ) -> tmux_cmd: """Execute tmux command respective of socket name and file, return output. @@ -227,8 +229,8 @@ def cmd( Renamed from ``.tmux`` to ``.cmd``. """ - svr_args: list[t.Union[str, int]] = [cmd] - cmd_args: list[t.Union[str, int]] = [] + svr_args: list[str | int] = [cmd] + cmd_args: list[str | int] = [] if self.socket_name: svr_args.insert(0, f"-L{self.socket_name}") if self.socket_path: @@ -311,7 +313,7 @@ def kill(self) -> None: """ self.cmd("kill-server") - def kill_session(self, target_session: t.Union[str, int]) -> "Server": + def kill_session(self, target_session: str | int) -> Server: """Kill tmux session. Parameters @@ -354,7 +356,7 @@ def switch_client(self, target_session: str) -> None: if proc.stderr: raise exc.LibTmuxException(proc.stderr) - def attach_session(self, target_session: t.Optional[str] = None) -> None: + def attach_session(self, target_session: str | None = None) -> None: """Attach tmux session. Parameters @@ -374,15 +376,15 @@ def attach_session(self, target_session: t.Optional[str] = None) -> None: def new_session( self, - session_name: t.Optional[str] = None, + session_name: str | None = None, kill_session: bool = False, attach: bool = False, - start_directory: t.Optional[str] = None, - window_name: t.Optional[str] = None, - window_command: t.Optional[str] = None, - x: t.Optional[t.Union[int, "DashLiteral"]] = None, - y: t.Optional[t.Union[int, "DashLiteral"]] = None, - environment: t.Optional[dict[str, str]] = None, + start_directory: str | None = None, + window_name: str | None = None, + window_command: str | None = None, + x: int | DashLiteral | None = None, + y: int | DashLiteral | None = None, + environment: dict[str, str] | None = None, *args: t.Any, **kwargs: t.Any, ) -> Session: @@ -476,7 +478,7 @@ def new_session( if env: del os.environ["TMUX"] - tmux_args: tuple[t.Union[str, int], ...] = ( + tmux_args: tuple[str | int, ...] = ( "-P", "-F#{session_id}", # output ) @@ -613,7 +615,9 @@ def __repr__(self) -> str: ) if self.socket_path is not None: return f"{self.__class__.__name__}(socket_path={self.socket_path})" - return f"{self.__class__.__name__}(socket_path=/tmp/tmux-1000/default)" + return ( + f"{self.__class__.__name__}(socket_path=/tmp/tmux-{os.geteuid()}/default)" + ) # # Legacy: Redundant stuff we want to remove @@ -655,7 +659,7 @@ def _list_panes(self) -> list[PaneDict]: ) return [p.__dict__ for p in self.panes] - def _update_panes(self) -> "Server": + def _update_panes(self) -> Server: """Update internal pane data and return ``self`` for chainability. .. deprecated:: 0.16 @@ -674,7 +678,7 @@ def _update_panes(self) -> "Server": self._list_panes() return self - def get_by_id(self, session_id: str) -> t.Optional[Session]: + def get_by_id(self, session_id: str) -> Session | None: """Return session by id. Deprecated in favor of :meth:`.sessions.get()`. .. deprecated:: 0.16 @@ -707,7 +711,7 @@ def where(self, kwargs: dict[str, t.Any]) -> list[Session]: except IndexError: return [] - def find_where(self, kwargs: dict[str, t.Any]) -> t.Optional[Session]: + def find_where(self, kwargs: dict[str, t.Any]) -> Session | None: """Filter through sessions, return first :class:`Session`. .. deprecated:: 0.16 @@ -742,7 +746,7 @@ def _list_windows(self) -> list[WindowDict]: ) return [w.__dict__ for w in self.windows] - def _update_windows(self) -> "Server": + def _update_windows(self) -> Server: """Update internal window data and return ``self`` for chainability. .. deprecated:: 0.16 @@ -774,7 +778,7 @@ def _sessions(self) -> list[SessionDict]: ) return self._list_sessions() - def _list_sessions(self) -> list["SessionDict"]: + def _list_sessions(self) -> list[SessionDict]: """Return list of session object dictionaries. .. deprecated:: 0.16 @@ -807,7 +811,7 @@ def list_sessions(self) -> list[Session]: return self.sessions @property - def children(self) -> QueryList["Session"]: + def children(self) -> QueryList[Session]: """Was used by TmuxRelationalObject (but that's longer used in this class). .. deprecated:: 0.16 diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 785459fd1..1b1fc260d 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -5,6 +5,8 @@ """ +from __future__ import annotations + import dataclasses import logging import pathlib @@ -12,7 +14,6 @@ import warnings from libtmux._internal.query_list import QueryList -from libtmux.common import tmux_cmd from libtmux.constants import WINDOW_DIRECTION_FLAG_MAP, WindowDirection from libtmux.formats import FORMAT_SEPARATOR from libtmux.neo import Obj, fetch_obj, fetch_objs @@ -30,6 +31,8 @@ ) if t.TYPE_CHECKING: + from libtmux.common import tmux_cmd + from .server import Server @@ -73,7 +76,7 @@ class Session(Obj, EnvironmentMixin): https://man.openbsd.org/tmux.1#DESCRIPTION. Accessed April 1st, 2018. """ - server: "Server" + server: Server def refresh(self) -> None: """Refresh session attributes from tmux.""" @@ -85,7 +88,7 @@ def refresh(self) -> None: ) @classmethod - def from_session_id(cls, server: "Server", session_id: str) -> "Session": + def from_session_id(cls, server: Server, session_id: str) -> Session: """Create Session from existing session_id.""" session = fetch_obj( obj_key="session_id", @@ -99,7 +102,7 @@ def from_session_id(cls, server: "Server", session_id: str) -> "Session": # Relations # @property - def windows(self) -> QueryList["Window"]: + def windows(self) -> QueryList[Window]: """Windows contained by session. Can be accessed via @@ -119,7 +122,7 @@ def windows(self) -> QueryList["Window"]: return QueryList(windows) @property - def panes(self) -> QueryList["Pane"]: + def panes(self) -> QueryList[Pane]: """Panes contained by session's windows. Can be accessed via @@ -145,7 +148,7 @@ def cmd( self, cmd: str, *args: t.Any, - target: t.Optional[t.Union[str, int]] = None, + target: str | int | None = None, ) -> tmux_cmd: """Execute tmux subcommand within session context. @@ -193,9 +196,9 @@ def cmd( def set_option( self, option: str, - value: t.Union[str, int], + value: str | int, global_: bool = False, - ) -> "Session": + ) -> Session: """Set option ``$ tmux set-option