Skip to content
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

asyncio: Asynchronous libtmux #554

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

asyncio: Asynchronous libtmux #554

wants to merge 4 commits into from

Conversation

tony
Copy link
Member

@tony tony commented Dec 25, 2024

Docs

Asyncio

See also

Summary by Sourcery

Introduce asynchronous support for libtmux using asyncio.

New Features:

  • Add AsyncTmuxCmd to run tmux commands asynchronously.
  • Add acmd method to Server, Session, Window, and Pane objects for asynchronous command execution.

Tests:

  • Add tests for asynchronous operations.

@tony tony force-pushed the asyncio branch 4 times, most recently from e7fdf41 to 1d31a1f Compare December 25, 2024 14:16
Copy link

codecov bot commented Dec 25, 2024

Codecov Report

Attention: Patch coverage is 62.90323% with 23 lines in your changes missing coverage. Please review.

Project coverage is 86.16%. Comparing base (c2108ab) to head (1103e6a).

Files with missing lines Patch % Lines
src/libtmux/server.py 35.29% 7 Missing and 4 partials ⚠️
src/libtmux/common.py 70.96% 7 Missing and 2 partials ⚠️
src/libtmux/pane.py 80.00% 0 Missing and 1 partial ⚠️
src/libtmux/session.py 75.00% 0 Missing and 1 partial ⚠️
src/libtmux/window.py 80.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #554      +/-   ##
==========================================
+ Coverage   82.13%   86.16%   +4.02%     
==========================================
  Files          36       36              
  Lines        3930     3990      +60     
  Branches      360      371      +11     
==========================================
+ Hits         3228     3438     +210     
+ Misses        551      385     -166     
- Partials      151      167      +16     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tony
Copy link
Member Author

tony commented Jan 11, 2025

@sourcery-ai review

Copy link

sourcery-ai bot commented Jan 11, 2025

Reviewer's Guide by Sourcery

This pull request introduces asynchronous functionality to libtmux using Python's asyncio library. It adds a new AsyncTmuxCmd class for running tmux commands asynchronously and integrates it into the existing Server, Session, Window, and Pane classes. It also updates the project dependencies and adds asynchronous tests.

Sequence diagram for async tmux command execution

sequenceDiagram
    participant C as Client Code
    participant A as AsyncTmuxCmd
    participant S as Subprocess
    participant T as tmux

    C->>+A: run(*args)
    A->>A: validate tmux binary
    A->>+S: create_subprocess_exec
    S->>+T: execute command
    T-->>-S: command output
    S-->>-A: stdout, stderr, returncode
    A->>A: process output
    A-->>-C: AsyncTmuxCmd instance
Loading

Class diagram for new AsyncTmuxCmd and its integration

classDiagram
    class AsyncTmuxCmd {
        +list[str] cmd
        +list[str] stdout
        +list[str] stderr
        +int returncode
        +__init__(cmd, stdout, stderr, returncode)
        +run(*args) AsyncTmuxCmd
    }

    class Server {
        +cmd(cmd, *args, target)
        +acmd(cmd, *args, target) AsyncTmuxCmd
    }

    class Session {
        +cmd(cmd, *args, target)
        +acmd(cmd, *args, target) AsyncTmuxCmd
    }

    class Window {
        +cmd(cmd, *args, target)
        +acmd(cmd, *args, target) AsyncTmuxCmd
    }

    class Pane {
        +cmd(cmd, *args, target)
        +acmd(cmd, *args, target) AsyncTmuxCmd
    }

    Server --> AsyncTmuxCmd : uses
    Session --> Server : uses
    Window --> Server : uses
    Pane --> Server : uses

    note for AsyncTmuxCmd "New async class for tmux commands"
    note for Server "Added async support via acmd"
Loading

File-Level Changes

Change Details Files
Added AsyncTmuxCmd class for asynchronous command execution.
  • Introduced the AsyncTmuxCmd class to encapsulate asynchronous execution of tmux commands.
  • Implemented the run method to handle subprocess creation and communication.
  • Added utility functions for string conversion and logging.
  • Included a workaround for the tmux has-session command behavior.
src/libtmux/common.py
Integrated asynchronous command execution into core classes.
  • Added acmd methods to the Server, Session, Window, and Pane classes for asynchronous command execution.
  • Updated existing methods to use the new asynchronous functionality.
  • Added examples and documentation for the new asynchronous methods.
src/libtmux/server.py
src/libtmux/session.py
src/libtmux/window.py
src/libtmux/pane.py
Updated project dependencies and added tests.
  • Added pytest-asyncio to the project dependencies for asynchronous testing.
  • Created a new test file tests/test_async.py with asynchronous test cases.
  • Updated the lock file to reflect the dependency changes.
pyproject.toml
tests/test_async.py
Updated documentation.
  • Added documentation for the new AsyncTmuxCmd class and its usage.
  • Updated the documentation for the Server class to include the new asynchronous methods.
src/libtmux/common.py
src/libtmux/server.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time. You can also use
    this command to specify where the summary should be inserted.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @tony - I've reviewed your changes - here's some feedback:

Overall Comments:

  • Consider adding more test cases to cover error conditions and different tmux command scenarios in test_async.py
Here's what I looked at during the review
  • 🟢 General issues: all looks good
  • 🟢 Security: all looks good
  • 🟡 Testing: 2 issues found
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

session_id=session_id,
server=server,
)
assert isinstance(session, Session)
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Test more aspects of the asynchronous functionality.

The current test only verifies the type of the returned session object. It would be beneficial to interact with the session asynchronously, for example, by creating a window or pane, executing a command within the session, and then verifying the results. This would provide more comprehensive coverage of the asynchronous API.

Suggested implementation:

@pytest.mark.asyncio
async def test_asyncio(server: Server) -> None:
    """Test basic asyncio usage."""
    # Create new session
    result = await server.acmd("new-session", "-d", "-P", "-F#{session_id}")
    session_id = result.stdout[0]
    session = Session.from_session_id(
        session_id=session_id,
        server=server,
    )
    assert isinstance(session, Session)

    # Create new window
    window = await session.new_window(window_name="test_window")
    assert window.name == "test_window"

    # Create new pane and execute command
    pane = await window.split_window()
    await pane.send_keys("echo 'Hello, tmux!'")

    # Allow time for command execution
    await asyncio.sleep(0.5)

    # Capture and verify output
    output = await pane.capture_pane()
    assert "Hello, tmux!" in '\n'.join(output)

    # Clean up
    await window.kill_window()

You may need to:

  1. Import asyncio if it's not already imported
  2. Adjust the sleep duration (0.5s) based on your system's performance
  3. Ensure the Session class has the async methods new_window(), and Window class has split_window() and kill_window()
  4. Ensure Pane class has send_keys() and capture_pane() async methods

@pytest.mark.asyncio
async def test_asyncio(server: Server) -> None:
"""Test basic asyncio usage."""
result = await server.acmd("new-session", "-d", "-P", "-F#{session_id}")
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Add tests for error handling.

Include tests to verify the behavior of the acmd function when the tmux command fails, for example, by trying to create a session with an already existing name. Check that the appropriate exceptions are raised and handled correctly.

Suggested implementation:

@pytest.mark.asyncio
async def test_asyncio(server: Server) -> None:
    """Test basic asyncio usage."""
    result = await server.acmd("new-session", "-d", "-P", "-F#{session_id}")
    session_id = result.stdout[0]
    session = Session.from_session_id(
        session_id=session_id,
        server=server,
    )
    assert isinstance(session, Session)


@pytest.mark.asyncio
async def test_asyncio_duplicate_session(server: Server) -> None:
    """Test error handling when creating duplicate sessions."""
    # Create first session
    session_name = "test_duplicate"
    await server.acmd("new-session", "-d", "-s", session_name)

    # Attempt to create second session with same name
    with pytest.raises(LibTmuxException) as excinfo:
        await server.acmd("new-session", "-d", "-s", session_name)

    assert "duplicate session" in str(excinfo.value).lower()


@pytest.mark.asyncio
async def test_asyncio_invalid_command(server: Server) -> None:
    """Test error handling with invalid tmux commands."""
    with pytest.raises(LibTmuxException) as excinfo:
        await server.acmd("invalid-command")

    assert "unknown command" in str(excinfo.value).lower()

You'll need to:

  1. Import LibTmuxException if not already imported (likely from libtmux.exc)
  2. Ensure any existing sessions are cleaned up in test teardown to prevent test interference

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant