reactive-firewall / multicast

The multicast package is a Python library that simplifies sending and receiving multicast network messages. It provides classes and tools for implementing multicast communication in Python applications, making it straightforward to work with multicast sockets.
Other
2 stars 0 forks source link

Implement '--message -' option for handling stdin in multicast #150

Open coderabbitai[bot] opened 2 weeks ago

coderabbitai[bot] commented 2 weeks ago

As discussed in PR #148 at this comment, the --message - function for handling stdin should be implemented. This should be implemented on a separate development branch first.

Requested by @reactive-firewall.

reactive-firewall commented 3 days ago

@coderabbitai please read through all the python files in the project. Then Re-read the file multicast/send.py focusing on how message data is currently passed to the eventual SAY_step. Also re-read multicast/__main__.py focusing on how multicast --use-std SAY --message - currently passes the message argument and message data to the eventual doStep of the SAY sub command. Finally explore how we could maintain current functionality while adding the functionality of reading from standard input or piped input when given the argument --message - as per the goal of this issue.

coderabbitai[bot] commented 3 days ago

[!TIP] For best results, initiate chat on the files or code changes.

@reactive-firewall I've reviewed the code for the multicast package, focusing on how message data is currently passed to the SAY_step and how the multicast --use-std SAY --message - command currently handles message arguments and data. Here's an analysis and suggestion for implementing the --message - functionality to read from standard input or piped input:

  1. Current Implementation:

    • In multicast/send.py, the McastSAY class handles the SAY operation.
    • The setupArgs method sets up the argument parser, including the --message option.
    • The doStep method calls _sayStep to send the message.
  2. Handling --message -: To implement reading from stdin when --message - is provided, we need to modify the McastSAY class. Here's a suggested implementation:

import sys

class McastSAY(multicast.mtool):
    # ... existing code ...

    @classmethod
    def setupArgs(cls, parser):
        if parser is not None:
            parser.add_argument(
                """--port""", type=int,
                default=multicast._MCAST_DEFAULT_PORT
            )
            parser.add_argument(
                """--group""",
                default=multicast._MCAST_DEFAULT_GROUP
            )
            parser.add_argument(
                """-m""", """--message""", nargs='+', dest="""data""",
                default=str("""PING from {name}: group: {group}, port: {port}""")
            )

    def doStep(self, *args, **kwargs):
        group = kwargs.get("group", [multicast._MCAST_DEFAULT_GROUP])
        port = kwargs.get("port", multicast._MCAST_DEFAULT_PORT)
        data = kwargs.get("data")

        if data == ['-']:
            # Read from stdin
            data = sys.stdin.read().strip()
        elif isinstance(data, list):
            # Join multiple arguments into a single string
            data = ' '.join(data)
        else:
            data = str(data)

        return self._sayStep(group, port, data)

    @staticmethod
    def _sayStep(group, port, data):
        # ... existing implementation ...
  1. Changes Explained:

    • In setupArgs, we keep the existing argument setup.
    • In doStep, we add logic to handle the --message - case:
      • If data is ['-'], we read from stdin.
      • If data is a list (multiple arguments), we join them into a single string.
      • Otherwise, we convert data to a string.
    • The _sayStep method remains unchanged.
  2. Main Script Changes: In multicast/__main__.py, ensure that the --use-std flag is properly passed to the McastSAY instance. This flag doesn't need to change the behavior of McastSAY itself but might be useful for other parts of the script.

  3. Testing: You should add unit tests to verify this new functionality. Here's an example test case:

import io
import sys
from unittest.mock import patch

def test_mcast_say_stdin():
    say = McastSAY()
    test_input = "Test message from stdin"

    with patch('sys.stdin', io.StringIO(test_input)):
        result = say.doStep(data=['-'], group='224.0.0.1', port=12345)

    # Assert that the result is as expected
    # You might need to modify this based on what _sayStep returns
    assert result == (True, None)  # or whatever the expected output is

This implementation maintains the current functionality while adding the ability to read from stdin when --message - is provided. It should work with both direct input and piped input.

To fully implement this feature, you'll need to:

  1. Update the McastSAY class in multicast/send.py as shown above.
  2. Ensure that the --use-std flag is properly handled in multicast/__main__.py.
  3. Add appropriate unit tests to verify the new functionality.
  4. Update any relevant documentation to reflect this new feature.

Let me know if you need any clarification or have any questions about this implementation!

🔗 Analysis chain ---
🏁 Scripts executed The following scripts were executed for the analysis: Script: ```shell #!/bin/bash # Display the contents of 'send.py'. fd --type f --name 'send.py' --exec cat {} + # Display the contents of '__main__.py'. fd --type f --name '__main__.py' --exec cat {} + # Display the contents of all Python files in the project. fd --type f --extension py --exec cat {} + ``` Length of output: 210652 --- Script: ```shell #!/bin/bash # Corrected: Display the contents of 'send.py'. fd 'send.py' -x cat {} # Corrected: Display the contents of '__main__.py'. fd '__main__.py' -x cat {} # Display the contents of all Python files in the project. fd -e py -x cat {} ``` Length of output: 239896

🧠 Learnings used ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#0 File: :0-0 Timestamp: 2024-10-12T00:41:41.731Z Learning: For future scripts, reactive-firewall prefers to use only POSIX built-ins (like `printf` instead of `echo`) to align with CEP-8 conventions. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#0 File: :0-0 Timestamp: 2024-10-12T03:22:28.902Z Learning: For future scripts, reactive-firewall prefers to keep the shebang line with an extra space (e.g., `#! /bin/bash`) as per project style. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#110 File: docs/CI.md:32-32 Timestamp: 2024-10-08T21:59:30.537Z Learning: Reactive-firewall prefers to keep the word "own" in headings when emphasizing the reader's personal test. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#110 File: docs/CI.md:32-32 Timestamp: 2024-09-17T06:40:42.918Z Learning: Reactive-firewall prefers to keep the word "own" in headings when emphasizing the reader's personal test. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#0 File: :0-0 Timestamp: 2024-10-08T03:15:05.171Z Learning: For future reviews, remember that when drafting or updating CEP documents, it's important to adhere to the project's standard format and include all relevant sections such as Motivation, Detailed Explanation, Implementation Guidelines, Impact, and References. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#0 File: :0-0 Timestamp: 2024-10-08T21:59:30.537Z Learning: For future reviews, remember that when drafting or updating CEP documents, it's important to adhere to the project's standard format and include all relevant sections such as Motivation, Detailed Explanation, Implementation Guidelines, Impact, and References. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#110 File: docs/USAGE.md:66-66 Timestamp: 2024-10-08T21:59:30.537Z Learning: Reactive-firewall prefers to use "Caveat" as a heading instead of "Caution". ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#110 File: docs/USAGE.md:66-66 Timestamp: 2024-09-17T03:38:28.677Z Learning: Reactive-firewall prefers to use "Caveat" as a heading instead of "Caution". ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#118 File: tests/check_pip:92-197 Timestamp: 2024-10-08T21:59:30.537Z Learning: Reactive-firewall prefers defining functions before they are called in Bash scripts to avoid errors due to Bash's sequential interpretation. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#118 File: tests/check_pip:92-197 Timestamp: 2024-09-24T01:03:10.574Z Learning: Reactive-firewall prefers defining functions before they are called in Bash scripts to avoid errors due to Bash's sequential interpretation. ```
reactive-firewall commented 15 hours ago

@coderabbitai, I've implemented the discussed changes, and tested them locally, and have some initial feedback: while this approach works ... it blocks until the end of input, I'd prefer to pseudo-packetize the input from stdin in a loop and repeatetitvly call _sayStep to send each part and then exit the loop when the end of input is reached, this would align better with the use of UDP for near real-time transmissions

coderabbitai[bot] commented 15 hours ago

[!TIP] For best results, initiate chat on the files or code changes.

@reactive-firewall, thank you for the feedback! You're absolutely right—reading from stdin in a loop and pseudo-packetizing the input to repeatedly call _sayStep aligns better with UDP's near real-time transmission model.

Here's an updated implementation that reads from stdin line by line and sends each part immediately:

import sys

class McastSAY(multicast.mtool):
    # ... existing code ...

    def doStep(self, *args, **kwargs):
        group = kwargs.get("group", [multicast._MCAST_DEFAULT_GROUP])
        port = kwargs.get("port", multicast._MCAST_DEFAULT_PORT)
        data = kwargs.get("data")

        if data == ['-']:
            # Read from stdin in a loop
            for line in sys.stdin:
                message = line.rstrip('\n')
                if message:
                    self._sayStep(group, port, message)
        elif isinstance(data, list):
            # Join multiple arguments into a single string
            message = ' '.join(data)
            self._sayStep(group, port, message)
        else:
            message = str(data)
            self._sayStep(group, port, message)

    @staticmethod
    def _sayStep(group, port, data):
        # ... existing implementation ...

Explanation:

Alternate Chunk-Based Reading:

If you prefer to read stdin in fixed-size chunks, you can modify the loop as follows:

import sys

class McastSAY(multicast.mtool):
    # ... existing code ...

    def doStep(self, *args, **kwargs):
        group = kwargs.get("group", [multicast._MCAST_DEFAULT_GROUP])
        port = kwargs.get("port", multicast._MCAST_DEFAULT_PORT)
        data = kwargs.get("data")

        if data == ['-']:
            # Read from stdin in chunks
            while True:
                chunk = sys.stdin.read(1024)  # Read 1024 bytes at a time
                if not chunk:
                    break
                self._sayStep(group, port, chunk)
        elif isinstance(data, list):
            # Join multiple arguments into a single string
            message = ' '.join(data)
            self._sayStep(group, port, message)
        else:
            message = str(data)
            self._sayStep(group, port, message)

    @staticmethod
    def _sayStep(group, port, data):
        # ... existing implementation ...

Notes:

Testing:

You can test this functionality by piping data into your program:

echo -e "First message\nSecond message\nThird message" | multicast --use-std SAY --message -

Or by using a file:

multicast --use-std SAY --message - < messages.txt

Let me know if this implementation aligns with your expectations or if you'd like further adjustments!


(_/) (•ᴗ•) (___) I'm curious and eager to help!


✏️ Learnings added ``` Learnt from: reactive-firewall URL: https://github.com/reactive-firewall/multicast/issues/150 Timestamp: 2024-10-19T06:17:54.594Z Learning: For future implementations, when handling `--message -` in `McastSAY`, prefer to read from stdin in a loop and pseudo-packetize the input by repeatedly calling `_sayStep`, aligning with UDP's near real-time transmission model. ``` > Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

🧠 Learnings used ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#0 File: :0-0 Timestamp: 2024-10-12T00:41:41.731Z Learning: For future scripts, reactive-firewall prefers to use only POSIX built-ins (like `printf` instead of `echo`) to align with CEP-8 conventions. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#0 File: :0-0 Timestamp: 2024-10-12T03:22:28.902Z Learning: For future scripts, reactive-firewall prefers to keep the shebang line with an extra space (e.g., `#! /bin/bash`) as per project style. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#110 File: docs/CI.md:32-32 Timestamp: 2024-10-08T21:59:30.537Z Learning: Reactive-firewall prefers to keep the word "own" in headings when emphasizing the reader's personal test. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#110 File: docs/CI.md:32-32 Timestamp: 2024-09-17T06:40:42.918Z Learning: Reactive-firewall prefers to keep the word "own" in headings when emphasizing the reader's personal test. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#0 File: :0-0 Timestamp: 2024-10-08T03:15:05.171Z Learning: For future reviews, remember that when drafting or updating CEP documents, it's important to adhere to the project's standard format and include all relevant sections such as Motivation, Detailed Explanation, Implementation Guidelines, Impact, and References. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#0 File: :0-0 Timestamp: 2024-10-08T21:59:30.537Z Learning: For future reviews, remember that when drafting or updating CEP documents, it's important to adhere to the project's standard format and include all relevant sections such as Motivation, Detailed Explanation, Implementation Guidelines, Impact, and References. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#110 File: docs/USAGE.md:66-66 Timestamp: 2024-10-08T21:59:30.537Z Learning: Reactive-firewall prefers to use "Caveat" as a heading instead of "Caution". ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#110 File: docs/USAGE.md:66-66 Timestamp: 2024-09-17T03:38:28.677Z Learning: Reactive-firewall prefers to use "Caveat" as a heading instead of "Caution". ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#118 File: tests/check_pip:92-197 Timestamp: 2024-10-08T21:59:30.537Z Learning: Reactive-firewall prefers defining functions before they are called in Bash scripts to avoid errors due to Bash's sequential interpretation. ``` --- ``` Learnt from: reactive-firewall PR: reactive-firewall/multicast#118 File: tests/check_pip:92-197 Timestamp: 2024-09-24T01:03:10.574Z Learning: Reactive-firewall prefers defining functions before they are called in Bash scripts to avoid errors due to Bash's sequential interpretation. ```