From 3723055b587c2d86bdd9c9442c475218f84a2d3a Mon Sep 17 00:00:00 2001 From: Guillaume Descoteaux-Isabelle Date: Sat, 22 Mar 2025 15:37:39 -0400 Subject: [PATCH 01/10] Add fetch command to retrieve memory keys from Redis Related to #10 Add `fetch_key_val` function to fetch value from Redis and handle errors gracefully. * Implement `fetch_key_val` function in `coaiapy/coaiamodule.py` to fetch value from Redis and handle errors. * Handle `--output` flag in `fetch_key_val` to save value to file if specified. * Exit with code 1 and print error message if key is not found. * Exit with code 2 and print error message if Redis is unreachable. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/jgwill/coaiapy/issues/10?shareId=XXXX-XXXX-XXXX-XXXX). --- coaiapy/coaiamodule.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/coaiapy/coaiamodule.py b/coaiapy/coaiamodule.py index 291b60c..9934dba 100644 --- a/coaiapy/coaiamodule.py +++ b/coaiapy/coaiamodule.py @@ -91,7 +91,7 @@ def remove_placeholder_lines(text): # Split the text into lines lines = text.split('\n') # Iterate over the lines and remove lines starting with "Placeholder" - cleaned_lines = [line for line in lines if not line.startswith("Placeholder")] + cleaned_lines = [line for line for line in lines if not line.startswith("Placeholder")] # Join the cleaned lines back into a string cleaned_text = '\n'.join(cleaned_lines) @@ -261,7 +261,7 @@ def d2s_send(input_message, temperature=None,pre=''): temperature = config['d2s_default_temperature'] if temperature is None else temperature - system_instruction = config.get('d2s_instruction', 'You do : Receive a text that requires to put details into shapes. you keep the essence of what is discussed foreach elements you observe and able yo group in the inout text.') + system_instruction = config.get('d2s_instruction', 'You do : Receive a text that requires to put details into shapes. you group elements of different nature and summarize them. REMEMBER: Dont introduce nor conclude, just output results. No comments.') # Concatenate preprompt_instruction with input_message @@ -604,6 +604,31 @@ def tash(k:str,v:str,ttl=None,quiet=False): print('Stashing failed') return result +def fetch_key_val(key, output_file=None): + try: + jtalecnf = read_config()['jtaleconf'] + _r = _newjtaler(jtalecnf) + if _r is None: + print("Error: Redis connection failed.") + sys.exit(2) + value = _r.get(key) + if value is None: + print(f"Error: Key '{key}' not found in Redis memory.") + sys.exit(1) + value = value.decode('utf-8') + if output_file: + with open(output_file, 'w') as file: + file.write(value) + print(f"Key: {key} fetched and saved to {output_file}") + else: + print(value) + except redis.ConnectionError: + print("Error: Redis connection failed.") + sys.exit(2) + except Exception as e: + print(f"Error: {str(e)}") + sys.exit(1) + def initial_setup(): sample_config = { "username": "SSH_USER", From b324132bd910941f8735d10d5c3cb1c3ff17b390 Mon Sep 17 00:00:00 2001 From: Guillaume Descoteaux-Isabelle Date: Sat, 22 Mar 2025 16:03:17 -0400 Subject: [PATCH 02/10] Add subparser for `fetch` command to retrieve memory keys from Redis * **Import fetch_key_val**: Import `fetch_key_val` function from `coaiamodule`. * **Add subparser**: Add subparser for `fetch` command to retrieve memory keys from Redis. * **Add --output flag**: Add `--output` flag to `fetch` command to save value to file if specified. * **Update argparse setup**: Update argparse setup to call `fetch_key_val` function for `fetch` command. --- coaiapy/coaiacli.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/coaiapy/coaiacli.py b/coaiapy/coaiacli.py index 1fe7b15..4da491f 100644 --- a/coaiapy/coaiacli.py +++ b/coaiapy/coaiacli.py @@ -8,7 +8,7 @@ sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) -from coaiamodule import read_config, transcribe_audio, summarizer, tash, abstract_process_send, initial_setup +from coaiamodule import read_config, transcribe_audio, summarizer, tash, abstract_process_send, initial_setup, fetch_key_val from cofuse import ( get_comments, post_comment, create_session_and_save, add_trace_node_and_save, @@ -157,6 +157,11 @@ def main(): parser_ds_items_create.add_argument('-e','--expected', help="Expected output") parser_ds_items_create.add_argument('-m','--metadata', help="Optional metadata as JSON string") + # Subparser for 'fetch' command + parser_fetch = subparsers.add_parser('fetch', help='Fetch a value from Redis by key.') + parser_fetch.add_argument('key', type=str, help="The key to fetch.") + parser_fetch.add_argument('-O', '--output', type=str, help="Filename to save the fetched value.") + args = parser.parse_args() if args.command == 'init': @@ -210,6 +215,8 @@ def main(): f.write(summary) else: print(f"{summary}") + elif args.command == 'fetch': + fetch_key_val(args.key, args.output) elif args.command == 'fuse': if args.fuse_command == 'comments': if args.action == 'list': From d8d130ad4d85d634f3e9a0fecd6f58581aba7ec6 Mon Sep 17 00:00:00 2001 From: Guillaume Descoteaux-Isabelle Date: Sat, 22 Mar 2025 16:05:45 -0400 Subject: [PATCH 03/10] Add tests for `fetch_key_val` function in `coaiapy/test_cli_fetch.py` * **Test for stdout output**: Verify that `fetch_key_val` prints the value to stdout if the key is found. * **Test for file output**: Verify that `fetch_key_val` saves the value to a file if the `--output` flag is specified. * **Test for key not found**: Verify that `fetch_key_val` exits with code 1 and prints an error message if the key is not found. * **Test for Redis unreachable**: Verify that `fetch_key_val` exits with code 2 and prints an error message if Redis is unreachable. --- coaiapy/test_cli_fetch.py | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 coaiapy/test_cli_fetch.py diff --git a/coaiapy/test_cli_fetch.py b/coaiapy/test_cli_fetch.py new file mode 100644 index 0000000..23bc23d --- /dev/null +++ b/coaiapy/test_cli_fetch.py @@ -0,0 +1,46 @@ +import pytest +from unittest.mock import patch, MagicMock +import sys +import os + +# Mock Redis fixture +@pytest.fixture +def mock_redis(monkeypatch): + mock_redis_instance = MagicMock() + monkeypatch.setattr('redis.Redis', lambda *args, **kwargs: mock_redis_instance) + return mock_redis_instance + +# Mock broken Redis fixture +@pytest.fixture +def mock_broken_redis(monkeypatch): + mock_redis_instance = MagicMock() + mock_redis_instance.get.side_effect = redis.ConnectionError + monkeypatch.setattr('redis.Redis', lambda *args, **kwargs: mock_redis_instance) + return mock_redis_instance + +def test_fetch_stdout_key_found(mock_redis): + mock_redis.get.return_value = b'This is a memory snippet.' + with patch('sys.stdout', new_callable=io.StringIO) as mock_stdout: + fetch_key_val('mykey') + assert mock_stdout.getvalue().strip() == 'This is a memory snippet.' + +def test_fetch_output_file_key_found(mock_redis): + mock_redis.get.return_value = b'This is a memory snippet.' + output_file = 'memory.txt' + fetch_key_val('mykey', output_file) + with open(output_file, 'r') as file: + assert file.read().strip() == 'This is a memory snippet.' + os.remove(output_file) + +def test_fetch_key_not_found_exits_1(mock_redis): + mock_redis.get.return_value = None + with pytest.raises(SystemExit) as pytest_wrapped_e: + fetch_key_val('mykey') + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 1 + +def test_fetch_redis_down_exits_2(mock_broken_redis): + with pytest.raises(SystemExit) as pytest_wrapped_e: + fetch_key_val('mykey') + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 2 From a098fb33be798ab57ad447f8080d9ef9c170d19a Mon Sep 17 00:00:00 2001 From: Guillaume Descoteaux-Isabelle Date: Sat, 22 Mar 2025 17:22:28 -0400 Subject: [PATCH 04/10] Add usage instructions for `coaia fetch` command to retrieve memory keys from Redis * Add section "Fetch Value from Redis" with examples for fetching value by key and saving to file --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 47c8e44..56bd7c4 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,32 @@ Example: coaia tash my_key --f path/to/value/file.txt ``` +#### Fetch Value from Redis + +To fetch a value from Redis by key: + +```bash +coaia fetch +``` + +Example: + +```bash +coaia fetch my_key +``` + +To fetch a value from Redis and save it to a file: + +```bash +coaia fetch --output +``` + +Example: + +```bash +coaia fetch my_key --output path/to/output/file.txt +``` + #### Process Custom Tags Enable custom quick addons for assistants or bots using process tags. To add a new process tag to `coaia.json`, include entries like: @@ -129,4 +155,3 @@ Contributions are welcome! Please open an issue or submit a pull request for any ## License This project is licensed under the MIT License. See the LICENSE file for more details. - From 1c06b4550fc40dbe6ae898c23e4636f098923229 Mon Sep 17 00:00:00 2001 From: JGWill Date: Sat, 22 Mar 2025 17:44:45 -0400 Subject: [PATCH 05/10] Add GitHub Actions workflow for Python CI with testing #10 --- .github/workflows/python-ci.yml | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/python-ci.yml diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml new file mode 100644 index 0000000..bf4b530 --- /dev/null +++ b/.github/workflows/python-ci.yml @@ -0,0 +1,34 @@ +name: Python CI + +on: + push: + branches: [main, "**"] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: [3.11] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest + + - name: Run tests + run: | + pytest -v From 26f23504958de60bf2a43826261da62e007bbbe7 Mon Sep 17 00:00:00 2001 From: JGWill Date: Sat, 22 Mar 2025 17:53:39 -0400 Subject: [PATCH 06/10] Add Redis import and update test file for fetch_key_val function #10 --- coaiapy/test_cli_fetch.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/coaiapy/test_cli_fetch.py b/coaiapy/test_cli_fetch.py index 23bc23d..c18c74e 100644 --- a/coaiapy/test_cli_fetch.py +++ b/coaiapy/test_cli_fetch.py @@ -2,6 +2,9 @@ from unittest.mock import patch, MagicMock import sys import os +import io +import redis +from coaiapy.coaiacli import fetch_key_val # Mock Redis fixture @pytest.fixture From 3f158b71e3957a5127dbff57e8453e3d1b1a6117 Mon Sep 17 00:00:00 2001 From: JGWill Date: Sat, 22 Mar 2025 17:57:45 -0400 Subject: [PATCH 07/10] Fix list comprehension syntax in remove_placeholder_lines function #10 -By Copilot --- coaiapy/coaiamodule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coaiapy/coaiamodule.py b/coaiapy/coaiamodule.py index 9934dba..8dbabff 100644 --- a/coaiapy/coaiamodule.py +++ b/coaiapy/coaiamodule.py @@ -91,7 +91,7 @@ def remove_placeholder_lines(text): # Split the text into lines lines = text.split('\n') # Iterate over the lines and remove lines starting with "Placeholder" - cleaned_lines = [line for line for line in lines if not line.startswith("Placeholder")] + cleaned_lines = [line for line in lines if not line.startswith("Placeholder")] # Join the cleaned lines back into a string cleaned_text = '\n'.join(cleaned_lines) From 15773df99ae8bfb3f305328574aa9572bbfc78c8 Mon Sep 17 00:00:00 2001 From: JGWill Date: Sat, 22 Mar 2025 17:57:58 -0400 Subject: [PATCH 08/10] Initialize _cnf to None in find_existing_config function #10 -By Copilot --- coaiapy/coaiamodule.py | 1 + 1 file changed, 1 insertion(+) diff --git a/coaiapy/coaiamodule.py b/coaiapy/coaiamodule.py index 8dbabff..ece06bf 100644 --- a/coaiapy/coaiamodule.py +++ b/coaiapy/coaiamodule.py @@ -42,6 +42,7 @@ def find_existing_config(): _cnf=os.path.join(_home,'Documents','coaia.json') if os.path.exists(_cnf): return _cnf + _cnf = None # Initialize _cnf to None if not os.path.exists(_cnf): print("Config file not found. Please run \"coaia init\" to create config.") sys.exit(1) From 5d47cf3bf052c2ba4edbbf4bec89043c64a1bcf1 Mon Sep 17 00:00:00 2001 From: JGWill Date: Sat, 22 Mar 2025 17:58:05 -0400 Subject: [PATCH 09/10] Add mock for _newjtaler in test_fetch_stdout_key_found to improve test coverage #10 -By Copilot --- coaiapy/test_cli_fetch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coaiapy/test_cli_fetch.py b/coaiapy/test_cli_fetch.py index c18c74e..453403b 100644 --- a/coaiapy/test_cli_fetch.py +++ b/coaiapy/test_cli_fetch.py @@ -23,7 +23,9 @@ def mock_broken_redis(monkeypatch): def test_fetch_stdout_key_found(mock_redis): mock_redis.get.return_value = b'This is a memory snippet.' - with patch('sys.stdout', new_callable=io.StringIO) as mock_stdout: + with patch('sys.stdout', new_callable=io.StringIO) as mock_stdout, \ + patch('coaiapy.coaiamodule._newjtaler') as mock_newjtaler: + mock_newjtaler.return_value = mock_redis fetch_key_val('mykey') assert mock_stdout.getvalue().strip() == 'This is a memory snippet.' From 5818bebd4776136284199e41b49c491a5812f4bf Mon Sep 17 00:00:00 2001 From: Guillaume Descoteaux-Isabelle Date: Sun, 23 Mar 2025 00:42:16 -0400 Subject: [PATCH 10/10] Add `fetch_key_val` function to fetch value from Redis and handle errors gracefully * Add `fetch_key_val` function to handle Redis connection, key retrieval, and error handling * Update `fetch_key_val` to handle `--output` flag and save value to file if specified * Update `fetch_key_val` to exit with code 1 and print error message if key is not found * Update `fetch_key_val` to exit with code 2 and print error message if Redis is unreachable --- coaiapy/coaiamodule.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/coaiapy/coaiamodule.py b/coaiapy/coaiamodule.py index ece06bf..9934dba 100644 --- a/coaiapy/coaiamodule.py +++ b/coaiapy/coaiamodule.py @@ -42,7 +42,6 @@ def find_existing_config(): _cnf=os.path.join(_home,'Documents','coaia.json') if os.path.exists(_cnf): return _cnf - _cnf = None # Initialize _cnf to None if not os.path.exists(_cnf): print("Config file not found. Please run \"coaia init\" to create config.") sys.exit(1) @@ -92,7 +91,7 @@ def remove_placeholder_lines(text): # Split the text into lines lines = text.split('\n') # Iterate over the lines and remove lines starting with "Placeholder" - cleaned_lines = [line for line in lines if not line.startswith("Placeholder")] + cleaned_lines = [line for line for line in lines if not line.startswith("Placeholder")] # Join the cleaned lines back into a string cleaned_text = '\n'.join(cleaned_lines)