Skip to content
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
29 changes: 26 additions & 3 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade --group pylint
pip install --upgrade -r CI_REQUIREMENTS.txt
pip install --upgrade "pylint >= 3.0.0"
- name: Install develop
run: |
pip install -e .
Expand Down Expand Up @@ -93,13 +93,36 @@ jobs:
pip install -e .
- name: Lint with MyPy
run: |
mypy --strict --install-types --non-interactive exec_helpers
mypy -p exec_helpers

PyRefly:
permissions:
contents: read
pull-requests: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade --group pyrefly
pip install --upgrade -r CI_REQUIREMENTS.txt
- name: Install develop
run: |
pip install -e .
- name: Lint with PyRefly
run: |
pyrefly check

Test:
permissions:
contents: read
pull-requests: read
needs: [PEP8, Formatting, PyLint, MyPy]
needs: [PEP8, Formatting, PyLint, MyPy, PyRefly]
runs-on: ${{ matrix.os }}
strategy:
max-parallel: 6
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repos:

- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.15.13
rev: v0.17.3
hooks:
# Run the linter.
- id: ruff-check
Expand All @@ -29,6 +29,6 @@ repos:
- repo: https://github.com/tox-dev/pyproject-fmt
# Use the sha / tag you want to point at
# or use `pre-commit autoupdate` to get the latest version
rev: "v2.21.2"
rev: "v2.30.3"
hooks:
- id: pyproject-fmt
6 changes: 5 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,11 @@ Keyword arguments:
- cwd - working directory.
- env - environment variables dict.

.. note:: `shell=true` is always set.
.. note:: The command is executed by the shell by default.
`Subprocess(shell=False)` (and `async_api.Subprocess(shell=False)`) executes it directly instead:
the command must be a list of arguments (a string raises `TypeError`),
and no shell syntax, expansion or `sh -c` inside a chroot is involved.
`SSHClient` can not do this: the remote side always passes the command to a shell.

async_api.Subprocess specific
-----------------------------
Expand Down
6 changes: 5 additions & 1 deletion doc/source/SSHClient.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ API: SSHClient and SSHAuth.

SSHClient helper.

.. py:method:: __init__(host, port=22, username=None, password=None, *, auth=None, verbose=True, ssh_config=None, ssh_auth_map=None, sock=None, keepalive=1, allow_ssh_agent=True)
.. py:method:: __init__(host, port=22, username=None, password=None, *, auth=None, verbose=True, ssh_config=None, ssh_auth_map=None, sock=None, keepalive=1, allow_ssh_agent=True, connect_timeout=None)

:param host: remote hostname
:type host: ``str``
Expand All @@ -34,6 +34,9 @@ API: SSHClient and SSHAuth.
:type keepalive: int | bool
:param allow_ssh_agent: use SSH Agent if available
:type allow_ssh_agent: bool
:param connect_timeout: timeout of the TCP connection, the SSH banner, the key exchange and the authentication,
applied to every connection step (a connection attempt is retried 3 times with a 3 s pause)
:type connect_timeout: float | None

.. note:: auth has priority over username/password/private_keys
.. note::
Expand All @@ -52,6 +55,7 @@ API: SSHClient and SSHAuth.
.. versionchanged:: 7.0.0 keepalive_mode is removed
.. versionchanged:: 7.4.0 return of keepalive_mode to prevent mix with keepalive period. Default is `False`
.. versionchanged:: 8.0.0 expose SSH Agent usage override
.. versionchanged:: 8.2.1 added connect_timeout

.. py:attribute:: log_mask_re

Expand Down
2 changes: 2 additions & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Sphinx documentation build configuration."""

# exec-helpers documentation build configuration file, created by
# sphinx-quickstart on Sun Oct 30 13:40:52 2016.
#
Expand Down
5 changes: 2 additions & 3 deletions exec_helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@

from __future__ import annotations

import contextlib
import importlib
import typing
import warnings

try: # noqa: SIM105,S314,RUF100
with contextlib.suppress(ImportError):
from ._version import version as __version__ # noqa: F401
except ImportError:
pass

if typing.TYPE_CHECKING:
from collections.abc import Sequence
Expand Down
40 changes: 40 additions & 0 deletions exec_helpers/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

if typing.TYPE_CHECKING:
from collections.abc import Iterable
from collections.abc import Mapping


def string_bytes_bytearray_as_bytes(src: str | bytes | bytearray) -> bytes:
Expand Down Expand Up @@ -96,3 +97,42 @@ def chroot_command(command: str, chroot_path: str | None = None, chroot_exe: str
quoted_command = shlex.quote(command)
return f"{chroot_exe} {chroot_dst} sh -c {shlex.quote(f'eval {quoted_command}')}"
return command


def chroot_argv_command(command: str, chroot_path: str | None = None, chroot_exe: str | None = None) -> str:
"""Prepare command for chroot execution without a shell, only the arguments are joined.

:param command: Original command, a result of `shlex.join`.
:type command: str
:param chroot_path: chroot path.
:type chroot_path: str | None
:param chroot_exe: chroot executable, may carry its own arguments.
:type chroot_exe: str | None
:return: Command to be executed with chroot rules if applicable.
:rtype: str
"""
if chroot_path and chroot_path != "/":
return shlex.join([*shlex.split(chroot_exe or "chroot"), chroot_path.strip(), *shlex.split(command)])
return command


def repr_stdin(stdin: bytes | None) -> str:
"""Describe STDIN data for a debug string without exposing it.

:param stdin: Data for STDIN.
:type stdin: bytes | None
:return: Size of the data, as the content can hold secrets.
:rtype: str
"""
return "None" if stdin is None else f"<{len(stdin)} bytes>"


def repr_env(env: Mapping[str, str] | None) -> str:
"""Describe an environment for a debug string without exposing the values.

:param env: Process environment.
:type env: Mapping[str, str] | None
:return: Variable names only, as the values can hold secrets.
:rtype: str
"""
return "None" if env is None else f"<names: {sorted(env, key=repr)}>"
Loading
Loading