diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b6c646d6..8c85fd9e0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -84,9 +84,14 @@ jobs: - name: Install python dependencies run: | poetry install -E "test coverage lint" + - name: Lint with flake8 run: | poetry run flake8 + + - name: Lint with mypy + run: poetry run mypy . + - name: Test with pytest continue-on-error: ${{ matrix.tmux-version == 'master' }} run: | diff --git a/CHANGES b/CHANGES index 57e49cf5f..c00170864 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,12 @@ $ pip install --user --upgrade --pre libtmux - `retry()`: Add deprecation warning. This will be removed in 0.13.x ({issue}`368`, {issue}`372`) - New function `retry_until()`: Polls a callback function for a set period of time until it returns `True` or times out. By default it will raise {exc}`libtmux.exc.WaitTimeout`, with `raises=False` it will return `False`. Thank you @categulario! ({issue}`368`, {issue}`372`) +### Internals + +- {issue}`382` [mypy] support added: + + - Basic mypy tests now pass + ## libtmux 0.11.0 (2022-03-10) ### Compatibility diff --git a/docs/conf.py b/docs/conf.py index d80ee8b84..33d885f88 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,6 +4,7 @@ import sys from os.path import dirname, relpath from pathlib import Path +from typing import Dict, List import libtmux # NOQA from libtmux import test # NOQA @@ -16,7 +17,7 @@ sys.path.insert(0, str(cwd / "_ext")) # package data -about = {} +about: Dict = {} with open("../libtmux/__about__.py") as fp: exec(fp.read(), about) @@ -68,8 +69,8 @@ html_css_files = ["css/custom.css"] html_extra_path = ["manifest.json"] html_theme = "furo" -html_theme_path = [] -html_theme_options = { +html_theme_path: List = [] +html_theme_options: Dict = { "light_logo": "img/libtmux.svg", "dark_logo": "img/libtmux.svg", "footer_icons": [ diff --git a/docs/developing.md b/docs/developing.md index a31c9a79d..244b55b44 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -2,11 +2,17 @@ [poetry] is a required package to develop. -`git clone https://github.com/tmux-python/libtmux.git` +```console +$ git clone https://github.com/tmux-python/libtmux.git +``` -`cd libtmux` +```console +$ cd libtmux +``` -`poetry install -E "docs test coverage lint format"` +```console +$ poetry install -E "docs test coverage lint format" +``` Makefile commands prefixed with `watch_` will watch files and rerun. @@ -21,23 +27,122 @@ Rerun tests on file change: `make watch_test` (requires [entr(1)]) Default preview server: http://localhost:8023 +[sphinx-autobuild] will automatically build the docs, watch for file changes and launch a server. + +From home directory: `make start_docs` From inside `docs/`: `make start` + +[sphinx-autobuild]: https://github.com/executablebooks/sphinx-autobuild + +### Manual documentation (the hard way) + `cd docs/` and `make html` to build. `make serve` to start http server. -Helpers: -`make build_docs`, `make serve_docs` +Helpers: `make build_docs`, `make serve_docs` Rebuild docs on file change: `make watch_docs` (requires [entr(1)]) -Rebuild docs and run server via one terminal: `make dev_docs` (requires above, and a -`make(1)` with `-J` support, e.g. GNU Make) +Rebuild docs and run server via one terminal: `make dev_docs` (requires above, and a `make(1)` with +`-J` support, e.g. GNU Make) + +## Formatting + +The project uses [black] and [isort] (one after the other). Configurations are in `pyproject.toml` +and `setup.cfg`: + +- `make black isort`: Run `black` first, then `isort` to handle import nuances + +## Linting + +[flake8] and [mypy] run via CI in our GitHub Actions. See the configuration in `pyproject.toml` and +`setup.cfg`. + +### flake8 + +[flake8] provides fast, reliable, barebones styling and linting. + +````{tab} Command + +poetry: + +```console +$ poetry run flake8 +``` + +If you setup manually: + +```console +$ flake8 +``` + +```` + +````{tab} make + +```console +$ make flake8 +``` + +```` + +````{tab} Watch + +```console +$ make watch_flake8 +``` + +requires [`entr(1)`]. -## Formatting / Linting +```` -The project uses [black] and [isort] (one after the other) and runs [flake8] via -CI. See the configuration in `pyproject.toml` and `setup.cfg`: +````{tab} Configuration -`make black isort`: Run `black` first, then `isort` to handle import nuances -`make flake8`, to watch (requires `entr(1)`): `make watch_flake8` +See `[flake8]` in setup.cfg. + +```{literalinclude} ../setup.cfg +:language: ini +:start-at: "[flake8]" +:end-before: "[isort]" + +``` + +```` + +### mypy + +[mypy] is used for static type checking. + +````{tab} Command + +poetry: + +```console +$ poetry run mypy . +``` + +If you setup manually: + +```console +$ mypy . +``` + +```` + +````{tab} make + +```console +$ make mypy +``` + +```` + +````{tab} Watch + +```console +$ make watch_mypy +``` + +requires [`entr(1)`]. +```` ## Releasing @@ -68,23 +173,18 @@ the top:: `libtmux/__init__.py` and `__about__.py` - Set version -`git commit -m 'Tag v0.9.1'` - -`git tag v0.9.1` - -`pip install wheel twine` - -`python setup.py sdist bdist_wheel` - -`twine upload dist/*` - -### Twine +```console +$ git commit -m 'Tag v0.9.1' +``` -`twine upload dist/*` +```console +$ git tag v0.9.1 +``` -You will be asked for PyPI login information. +After `git push` and `git push --tags`, CI will automatically build and deploy +to PyPI. -### Releasing with Poetry (hypothetical) +### Releasing with Poetry (manual) This isn't used yet since package maintainers may want setup.py in the source. See https://github.com/tmux-python/tmuxp/issues/625. @@ -104,6 +204,8 @@ Update `__version__` in `__about__.py` and `pyproject.toml`:: [twine]: https://twine.readthedocs.io/ [poetry]: https://python-poetry.org/ [entr(1)]: http://eradman.com/entrproject/ +[`entr(1)`]: http://eradman.com/entrproject/ [black]: https://github.com/psf/black [isort]: https://pypi.org/project/isort/ [flake8]: https://flake8.pycqa.org/ +[mypy]: http://mypy-lang.org/ diff --git a/libtmux/_compat.py b/libtmux/_compat.py index d16df9fb7..82ec43567 100644 --- a/libtmux/_compat.py +++ b/libtmux/_compat.py @@ -24,4 +24,4 @@ def str_from_console(s: t.Union[str, bytes]) -> str: try: return str(s) except UnicodeDecodeError: - return str(s, encoding="utf_8") + return str(s, encoding="utf_8") if isinstance(s, bytes) else s diff --git a/libtmux/server.py b/libtmux/server.py index 11f1c1c90..d1301aa30 100644 --- a/libtmux/server.py +++ b/libtmux/server.py @@ -534,7 +534,7 @@ def new_session( if env: del os.environ["TMUX"] - tmux_args = ( + tmux_args: t.Tuple = ( "-s%s" % session_name, "-P", "-F%s" % formats.FORMAT_SEPARATOR.join(tmux_formats), # output diff --git a/libtmux/session.py b/libtmux/session.py index 1c35d6388..ca7652461 100644 --- a/libtmux/session.py +++ b/libtmux/session.py @@ -209,7 +209,7 @@ def new_window( wformats = ["session_name", "session_id"] + formats.WINDOW_FORMATS tmux_formats = ["#{%s}" % f for f in wformats] - window_args = tuple() + window_args: t.Tuple = tuple() if not attach: window_args += ("-d",) diff --git a/libtmux/test.py b/libtmux/test.py index ad5494e2b..03ade73e4 100644 --- a/libtmux/test.py +++ b/libtmux/test.py @@ -2,7 +2,7 @@ import contextlib import logging import os -import tempfile +import random import time import warnings from typing import Callable, Optional @@ -15,13 +15,25 @@ RETRY_TIMEOUT_SECONDS = int(os.getenv("RETRY_TIMEOUT_SECONDS", 8)) RETRY_INTERVAL_SECONDS = float(os.getenv("RETRY_INTERVAL_SECONDS", 0.05)) -namer = tempfile._RandomNameSequence() + +class RandomStrSequence: + def __init__(self, characters: str = "abcdefghijklmnopqrstuvwxyz0123456789_"): + self.characters: str = characters + + def __iter__(self): + return self + + def __next__(self): + return "".join(random.sample(self.characters, k=8)) + + +namer = RandomStrSequence() current_dir = os.path.abspath(os.path.dirname(__file__)) example_dir = os.path.abspath(os.path.join(current_dir, "..", "examples")) fixtures_dir = os.path.realpath(os.path.join(current_dir, "fixtures")) -def retry(seconds: Optional[float] = RETRY_TIMEOUT_SECONDS) -> bool: +def retry(seconds: float = RETRY_TIMEOUT_SECONDS) -> bool: """ Retry a block of code until a time limit or ``break``. @@ -60,7 +72,7 @@ def retry_until( fun: Callable, seconds: float = RETRY_TIMEOUT_SECONDS, *, - interval: Optional[float] = RETRY_INTERVAL_SECONDS, + interval: float = RETRY_INTERVAL_SECONDS, raises: Optional[bool] = True, ) -> bool: """ diff --git a/libtmux/window.py b/libtmux/window.py index 65a083f0c..c70440df9 100644 --- a/libtmux/window.py +++ b/libtmux/window.py @@ -69,8 +69,7 @@ def __repr__(self): ) @property - def _info(self, *args): - + def _info(self): attrs = {"window_id": self._window_id} # from https://github.com/serkanyersen/underscore.py @@ -445,7 +444,7 @@ def split_window( # '-t%s' % self.attached_pane.get('pane_id'), # 2013-10-18 LOOK AT THIS, rm'd it.. - tmux_args = tuple() + tmux_args: t.Tuple = tuple() if target: tmux_args += ("-t%s" % target,) @@ -504,6 +503,7 @@ def attached_pane(self) -> t.Optional[Pane]: # for now pane_active is a unicode if "pane_active" in pane and pane.get("pane_active") == "1": return Pane(window=self, **pane) + return None def _list_panes(self) -> t.List[PaneDict]: panes = self.server._update_panes()._panes