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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- run: pip install -r requirements-travis.txt
- run: make install
- run: make check
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
prune debian
prune .github
prune docs
global-exclude *.py[cod] __pycache__ *.so *.spec *.txt .gitignore
111 changes: 53 additions & 58 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,100 +1,95 @@
PYTHON=$(shell which python 2>/dev/null || which python3 2>/dev/null)
DESTDIR=/
BUILDIR=$(CURDIR)/debian/aexpect
PROJECT=aexpect
VERSION="1.8.0"
VERSION=$(shell $(PYTHON) -m setuptools_scm)
COMMIT=$(shell git log --pretty=format:'%H' -n 1)
SHORT_COMMIT=$(shell git log --pretty=format:'%h' -n 1)
COMMIT_DATE=$(shell git log --pretty='format:%cd' --date='format:%Y%m%d' -n 1)
MOCK_CONFIG=default

all:
@echo "make check - Runs tree static check, unittests and functional tests. Some tests are only executed when AEXPECT_TIME_SENSITIVE=yes is set."
@echo "make clean - Get rid of scratch and byte files"
@echo "make source - Create source package"
@echo "make install - Install on local system"
@echo "make build-deb-src - Generate a source debian package"
@echo "make build-deb-bin - Generate a binary debian package"
@echo "make build-deb-all - Generate both source and binary debian packages"
@echo "RPM related targets:"
@echo "make srpm: Generate a source RPM package (.srpm)"
@echo "make rpm: Generate binary RPMs"
@echo "make check - Run lint and tests"
@echo "make clean - Remove build artifacts"
@echo "make source - Create source package (commit snapshot)"
@echo "make source-release - Create source package (versioned tag)"
@echo "make install - Install from built wheel"
@echo "make develop - Editable install"
@echo "make pypi - Build wheel+sdist (ready for upload)"
@echo
@echo "Release related targets:"
@echo "source-release: Create source package for the latest tagged release"
@echo "srpm-release: Generate a source RPM package (.srpm) for the latest tagged release"
@echo "rpm-release: Generate binary RPMs for the latest tagged release"
@echo "Debian targets: build-deb-src, build-deb-bin, build-deb-all"
@echo "RPM targets: srpm, rpm, srpm-release, rpm-release"

# --- Packaging ---
source: clean
if test ! -d SOURCES; then mkdir SOURCES; fi
git archive --prefix="aexpect-$(COMMIT)/" -o "SOURCES/aexpect-$(SHORT_COMMIT).tar.gz" HEAD
mkdir -p SOURCES
git archive --prefix="$(PROJECT)-$(COMMIT)/" -o "SOURCES/$(PROJECT)-$(SHORT_COMMIT).tar.gz" HEAD

source-release: clean
if test ! -d SOURCES; then mkdir SOURCES; fi
git archive --prefix="aexpect-$(VERSION)/" -o "SOURCES/aexpect-$(VERSION).tar.gz" $(VERSION)
mkdir -p SOURCES
git archive --prefix="$(PROJECT)-$(VERSION)/" -o "SOURCES/$(PROJECT)-$(VERSION).tar.gz" $(VERSION)
Comment on lines +27 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

source-release may fail if setuptools_scm strips a 'v' tag prefix

If your git tag is v1.8.0, setuptools_scm yields 1.8.0, and git archive $(VERSION) will fail. Resolve by mapping version to the tag commit.

Apply this diff to resolve within the current line:

-	git archive --prefix="$(PROJECT)-$(VERSION)/" -o "SOURCES/$(PROJECT)-$(VERSION).tar.gz" $(VERSION)
+	git archive --prefix="$(PROJECT)-$(VERSION)/" -o "SOURCES/$(PROJECT)-$(VERSION).tar.gz" $(shell git rev-list -n1 "refs/tags/$(VERSION)" 2>/dev/null || git rev-list -n1 "refs/tags/v$(VERSION)")

Run a quick check:

#!/bin/bash
# Expect at least one to resolve to a commit
git rev-parse -q --verify "refs/tags/$(python3 -m setuptools_scm)" || git rev-parse -q --verify "refs/tags/v$(python3 -m setuptools_scm)"
🤖 Prompt for AI Agents
In Makefile around lines 27-28, git archive uses $(VERSION) directly which
breaks when setuptools_scm strips a leading "v" from tags (e.g. tag v1.8.0 ->
VERSION 1.8.0); update the target to resolve the tag to a commit by checking
refs/tags/$(python3 -m setuptools_scm) and then refs/tags/v$(python3 -m
setuptools_scm) (falling back to $(VERSION) if neither exists) and use that
resolved ref in the git archive invocation so the archive refers to the actual
tag commit.


install:
$(PYTHON) setup.py install --root $(DESTDIR) $(COMPILE)
rm -r dist 2>/dev/null || true
$(PYTHON) -m pip install --upgrade pip build wheel
$(PYTHON) -m build
$(PYTHON) -m pip install --no-deps --force-reinstall dist/*.whl

develop:
$(PYTHON) -m pip install --editable .[dev]

pypi: clean
$(PYTHON) -m build
$(PYTHON) -m twine check dist/*
@echo
@echo
@echo "Use 'python3 -m twine upload dist/*'"
@echo "to upload this release"

# --- Checks ---
check: clean
inspekt checkall --disable-lint R0917,R0205,R0801,W4901,W0703,W0511 --disable-style E203,E501,E265,W601,E402 --exclude .venv*
$(PYTHON) -m black --check -- $(shell git ls-files -- "*.py")
$(PYTHON) -m isort --check-only -- $(shell git ls-files -- "*.py")
$(PYTHON) -m pytest

test: check

# --- Distro packaging (unchanged except cosmetic) ---
prepare-source:
# build the source package in the parent directory
# then rename it to project_version.orig.tar.gz
dch -D "utopic" -M -v "$(VERSION)" "Automated (make builddeb) build."
$(PYTHON) setup.py sdist $(COMPILE) --dist-dir=../
$(PYTHON) -m build --sdist --outdir ../
rename -f 's/$(PROJECT)-(.*)\.tar\.gz/$(PROJECT)_$$1\.orig\.tar\.gz/' ../*

build-deb-src: prepare-source
# build the source package
dpkg-buildpackage -S [email protected] -rfakeroot

build-deb-bin: prepare-source
# build binary package
dpkg-buildpackage -b -rfakeroot

build-deb-all: prepare-source
# build both source and binary packages
dpkg-buildpackage -i -I -rfakeroot

srpm: source
if test ! -d BUILD/SRPM; then mkdir -p BUILD/SRPM; fi
mock -r $(MOCK_CONFIG) --resultdir BUILD/SRPM -D "rel_build 0" -D "commit $(COMMIT)" -D "commit_date $(COMMIT_DATE)" --buildsrpm --spec python-aexpect.spec --sources SOURCES
mkdir -p BUILD/SRPM
mock -r $(MOCK_CONFIG) --resultdir BUILD/SRPM -D "rel_build 0" -D "commit $(COMMIT)" -D "commit_date $(COMMIT_DATE)" --buildsrpm --spec python-$(PROJECT).spec --sources SOURCES

rpm: srpm
if test ! -d BUILD/RPM; then mkdir -p BUILD/RPM; fi
mock -r $(MOCK_CONFIG) --resultdir BUILD/RPM -D "rel_build 0" -D "commit $(COMMIT)" -D "commit_date $(COMMIT_DATE)" --rebuild BUILD/SRPM/python-aexpect-$(VERSION)-*.src.rpm
mkdir -p BUILD/RPM
mock -r $(MOCK_CONFIG) --resultdir BUILD/RPM -D "rel_build 0" -D "commit $(COMMIT)" -D "commit_date $(COMMIT_DATE)" --rebuild BUILD/SRPM/python-$(PROJECT)-$(VERSION)-*.src.rpm

srpm-release: source-release
if test ! -d BUILD/SRPM; then mkdir -p BUILD/SRPM; fi
mock -r $(MOCK_CONFIG) --resultdir BUILD/SRPM -D "rel_build 1" --buildsrpm --spec python-aexpect.spec --sources SOURCES
mkdir -p BUILD/SRPM
mock -r $(MOCK_CONFIG) --resultdir BUILD/SRPM -D "rel_build 1" --buildsrpm --spec python-$(PROJECT).spec --sources SOURCES

rpm-release: srpm-release
if test ! -d BUILD/RPM; then mkdir -p BUILD/RPM; fi
mock -r $(MOCK_CONFIG) --resultdir BUILD/RPM -D "rel_build 1" --rebuild BUILD/SRPM/python-aexpect-$(VERSION)-*.src.rpm

check: clean
inspekt checkall --disable-lint R0917,R0205,R0801,W4901,W0703,W0511 --disable-style E203,E501,E265,W601,E402 --exclude .venv*
$(PYTHON) -m black --line-length 79 --check -- $(shell git ls-files -- "*.py")
$(PYTHON) -m pip install -e .
$(PYTHON) -m pytest tests
mkdir -p BUILD/RPM
mock -r $(MOCK_CONFIG) --resultdir BUILD/RPM -D "rel_build 1" --rebuild BUILD/SRPM/python-$(PROJECT)-$(VERSION)-*.src.rpm

clean:
$(PYTHON) setup.py clean
$(MAKE) -f $(CURDIR)/debian/rules clean || true
rm -rf build/ MANIFEST BUILD BUILDROOT SPECS RPMS SRPMS SOURCES
rm -rf .mypy_cache *.egg-info MANIFEST BUILD BUILDROOT SPECS RPMS SRPMS SOURCES dist
find . -name '*.pyc' -delete
find . -name '__pycache__' -delete

pypi: clean
if test ! -d PYPI_UPLOAD; then mkdir PYPI_UPLOAD; fi
$(PYTHON) setup.py bdist_wheel -d PYPI_UPLOAD
$(PYTHON) setup.py sdist -d PYPI_UPLOAD
@echo
@echo "Please use the files on PYPI_UPLOAD dir to upload a new version to PyPI"
@echo "The URL to do that may be a bit tricky to find, so here it is:"
@echo " https://pypi.python.org/pypi?%3Aaction=submit_form"
@echo
@echo "Alternatively, you can also run a command like: "
@echo " twine upload -u <PYPI_USERNAME> PYPI_UPLOAD/*.{tar.gz,whl}"
@echo

.PHONY: source install clean
.PHONY: all source source-release install develop pypi check test clean \
build-deb-src build-deb-bin build-deb-all srpm rpm srpm-release rpm-release prepare-source

41 changes: 21 additions & 20 deletions aexpect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,24 @@
entry-points.
"""

from .exceptions import ExpectError
from .exceptions import ExpectProcessTerminatedError
from .exceptions import ExpectTimeoutError
from .exceptions import ShellCmdError
from .exceptions import ShellError
from .exceptions import ShellProcessTerminatedError
from .exceptions import ShellStatusError
from .exceptions import ShellTimeoutError

from .client import Spawn
from .client import Tail
from .client import Expect
from .client import ShellSession
from .client import kill_tail_threads
from .client import run_tail
from .client import run_bg
from .client import run_fg

from . import remote
from . import rss_client
from . import remote, rss_client
from .client import (
Expect,
ShellSession,
Spawn,
Tail,
kill_tail_threads,
run_bg,
run_fg,
run_tail,
)
from .exceptions import (
ExpectError,
ExpectProcessTerminatedError,
ExpectTimeoutError,
ShellCmdError,
ShellError,
ShellProcessTerminatedError,
ShellStatusError,
ShellTimeoutError,
)
53 changes: 27 additions & 26 deletions aexpect/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,39 @@

# disable too-many-* as we need them pylint: disable=R0902,R0913,R0914,C0302

import time
import signal
import locale
import logging
import os
import re
import threading
import shutil
import select
import shutil
import signal
import subprocess
import locale
import logging
import threading
import time

from aexpect.exceptions import ExpectError
from aexpect.exceptions import ExpectProcessTerminatedError
from aexpect.exceptions import ExpectTimeoutError
from aexpect.exceptions import ShellCmdError
from aexpect.exceptions import ShellError
from aexpect.exceptions import ShellProcessTerminatedError
from aexpect.exceptions import ShellStatusError
from aexpect.exceptions import ShellTimeoutError

from aexpect.shared import BASE_DIR
from aexpect.shared import get_filenames
from aexpect.shared import get_reader_filename
from aexpect.shared import get_lock_fd
from aexpect.shared import is_file_locked
from aexpect.shared import unlock_fd
from aexpect.shared import wait_for_lock

from aexpect.utils import astring
from aexpect.utils import data_factory
from aexpect.utils import process as utils_process
from aexpect.exceptions import (
ExpectError,
ExpectProcessTerminatedError,
ExpectTimeoutError,
ShellCmdError,
ShellError,
ShellProcessTerminatedError,
ShellStatusError,
ShellTimeoutError,
)
from aexpect.shared import (
BASE_DIR,
get_filenames,
get_lock_fd,
get_reader_filename,
is_file_locked,
unlock_fd,
wait_for_lock,
)
from aexpect.utils import astring, data_factory
from aexpect.utils import path as utils_path
from aexpect.utils import process as utils_process
from aexpect.utils import wait as utils_wait

_THREAD_KILL_REQUESTED = threading.Event()
Expand Down
4 changes: 2 additions & 2 deletions aexpect/ops_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@
Network configuration and downloads.
"""

import logging
import re
import uuid
import logging
from textwrap import dedent
from base64 import b64encode
from enum import Enum, auto
from textwrap import dedent

# avocado imports
from aexpect.exceptions import ShellError, ShellProcessTerminatedError
Expand Down
9 changes: 4 additions & 5 deletions aexpect/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,15 @@
# ..todo:: we could reduce the disabled issues after more significant refactoring

from __future__ import division

import logging
import time
import re
import shlex
import time

from aexpect.client import Expect
from aexpect.client import RemoteSession
from aexpect.exceptions import ExpectTimeoutError
from aexpect.exceptions import ExpectProcessTerminatedError
from aexpect import rss_client
from aexpect.client import Expect, RemoteSession
from aexpect.exceptions import ExpectProcessTerminatedError, ExpectTimeoutError

LOG = logging.getLogger(__name__)

Expand Down
15 changes: 6 additions & 9 deletions aexpect/remote_door.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@
# disable too-many/few-* as we need them pylint: disable=R0903,R0912,R0913,R0914,R0915,R0917,C0302
# ..todo:: we could reduce the disabled issues after more significant refactoring

import importlib
import inspect
import logging
import os
import re
import logging
import inspect
import importlib
import threading
import tempfile
import threading
import time

# NOTE: enable this before importing the Pyro backend in order to debug issues
Expand All @@ -72,13 +72,10 @@
try:
try:
# noinspection PyPackageRequirements,PyUnresolvedReferences
from Pyro5.compatibility import Pyro4

# noinspection PyPackageRequirements
from Pyro5 import server

# noinspection PyPackageRequirements
from Pyro5 import nameserver
from Pyro5 import nameserver, server
from Pyro5.compatibility import Pyro4

NS_MODULE = "Pyro5.nameserver"
except ImportError:
Comment on lines +77 to 81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix: Pyro5 path may raise AttributeError due to missing server.is_private_attribute

In the Pyro5 branch, server.is_private_attribute may not exist, but it’s used later (e.g., in share_local_object). This will raise AttributeError at runtime when Pyro5 is available. Define a compatible predicate in the Pyro5 path.

Apply this diff after the Pyro5 imports:

 from Pyro5 import nameserver, server
 from Pyro5.compatibility import Pyro4

 NS_MODULE = "Pyro5.nameserver"
+        # Ensure attribute-filter API used below exists under Pyro5 as well
+        if not hasattr(server, "is_private_attribute"):
+            server.is_private_attribute = lambda name: name.startswith("_")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from Pyro5 import nameserver, server
from Pyro5.compatibility import Pyro4
NS_MODULE = "Pyro5.nameserver"
except ImportError:
from Pyro5 import nameserver, server
from Pyro5.compatibility import Pyro4
NS_MODULE = "Pyro5.nameserver"
# Ensure attribute-filter API used below exists under Pyro5 as well
if not hasattr(server, "is_private_attribute"):
server.is_private_attribute = lambda name: name.startswith("_")
except ImportError:
🤖 Prompt for AI Agents
In aexpect/remote_door.py around lines 77 to 81, the Pyro5 branch may import
server that lacks server.is_private_attribute but later code expects it; add a
compatibility definition immediately after the Pyro5 imports that defines
server.is_private_attribute when missing (e.g., assign it to an appropriate
predicate from nameserver or implement an equivalent function that matches the
expected signature) so later calls like share_local_object won't raise
AttributeError.

Expand Down
9 changes: 5 additions & 4 deletions aexpect/rss_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
# ..todo:: we could reduce the disabled issues after more significant refactoring

from __future__ import division, print_function

import argparse
import glob
import os
import socket
import struct
import time
import sys
import os
import glob
import argparse
import time

# Globals
CHUNKSIZE = 65536
Expand Down
Empty file added aexpect/scripts/__init__.py
Empty file.
Loading
Loading