1 Commits

Author SHA1 Message Date
juk0de 420423b36e hist_cmd: added module 'test_hist_cmd.py' 2023-09-29 18:53:12 +02:00
11 changed files with 34 additions and 54 deletions
-1
View File
@@ -106,7 +106,6 @@ celerybeat.pid
.venv .venv
env/ env/
venv/ venv/
.old/
ENV/ ENV/
env.bak/ env.bak/
venv.bak/ venv.bak/
+6 -7
View File
@@ -68,21 +68,20 @@ cmm question [-t OTAGS]... [-k ATAGS]... [-x XTAGS]... [-o OUTTAGS]... [-A AI_ID
#### Hist #### Hist
The `hist` command is used to print and manage the chat history. The `hist` command is used to print the chat history.
```bash ```bash
cmm hist [--print | --convert FORMAT] [-t OTAGS]... [-k ATAGS]... [-x XTAGS]... [-w] [-W] [-S] [-A SUBSTRING] [-Q SUBSTRING] cmm hist [-t OTAGS]... [-k ATAGS]... [-x XTAGS]... [-w] [-W] [-S] [-A SUBSTRING] [-Q SUBSTRING]
``` ```
* `-p, --print`: Print the DB chat history
* `-c, --convert FORMAT`: Convert all messages to the given format
* `-t, --or-tags OTAGS`: List of tags (one must match) * `-t, --or-tags OTAGS`: List of tags (one must match)
* `-k, --and-tags ATAGS`: List of tags (all must match) * `-k, --and-tags ATAGS`: List of tags (all must match)
* `-x, --exclude-tags XTAGS`: List of tags to exclude * `-x, --exclude-tags XTAGS`: List of tags to exclude
* `-w, --with-metadata`: Print chat history with metadata (tags, filenames, AI, etc.) * `-w, --with-tags`: Print chat history with tags
* `-W, --with-files`: Print chat history with filenames
* `-S, --source-code-only`: Only print embedded source code * `-S, --source-code-only`: Only print embedded source code
* `-A, --answer SUBSTRING`: Filter for answer substring * `-A, --answer SUBSTRING`: Search for answer substring
* `-Q, --question SUBSTRING`: Filter for question substring * `-Q, --question SUBSTRING`: Search for question substring
#### Tags #### Tags
+3 -6
View File
@@ -255,17 +255,14 @@ class Chat:
return sum(m.tokens() for m in self.messages) return sum(m.tokens() for m in self.messages)
def print(self, source_code_only: bool = False, def print(self, source_code_only: bool = False,
with_metadata: bool = False, with_tags: bool = False, with_files: bool = False,
paged: bool = True, paged: bool = True) -> None:
tight: bool = False) -> None:
output: list[str] = [] output: list[str] = []
for message in self.messages: for message in self.messages:
if source_code_only: if source_code_only:
output.append(message.to_str(source_code_only=True)) output.append(message.to_str(source_code_only=True))
continue continue
output.append(message.to_str(with_metadata)) output.append(message.to_str(with_tags, with_files))
if not tight:
output.append('\n' + ('-' * terminal_width()) + '\n')
if paged: if paged:
print_paged('\n'.join(output)) print_paged('\n'.join(output))
else: else:
+5 -4
View File
@@ -29,7 +29,7 @@ def convert_messages(args: argparse.Namespace, config: Config) -> None:
m.file_path = m.file_path.with_suffix('') m.file_path = m.file_path.with_suffix('')
chat.msg_write(msgs) chat.msg_write(msgs)
# read all messages with the current default suffix # read all messages with the current default suffix
msgs = chat.msg_gather(loc='disk', glob=f'*{msg_suffix}') msgs = chat.msg_gather(loc='disk', glob='*{msg_suffix}')
# make sure we converted all of the original messages # make sure we converted all of the original messages
for mid in msg_ids: for mid in msg_ids:
if not any(mid == m.msg_id() for m in msgs): if not any(mid == m.msg_id() for m in msgs):
@@ -41,6 +41,8 @@ def convert_messages(args: argparse.Namespace, config: Config) -> None:
if m.file_path and m.file_path.suffix != msg_suffix: if m.file_path and m.file_path.suffix != msg_suffix:
m.rm_file() m.rm_file()
print(f"Successfully converted {len(msg_ids)} messages.") print(f"Successfully converted {len(msg_ids)} messages.")
if len(msgs):
print(f"Deleted {len(msgs)} messages with deprecated suffixes.")
def print_chat(args: argparse.Namespace, config: Config) -> None: def print_chat(args: argparse.Namespace, config: Config) -> None:
@@ -57,9 +59,8 @@ def print_chat(args: argparse.Namespace, config: Config) -> None:
Path(config.db), Path(config.db),
mfilter=mfilter) mfilter=mfilter)
chat.print(args.source_code_only, chat.print(args.source_code_only,
args.with_metadata, args.with_tags,
paged=not args.no_paging, args.with_files)
tight=args.tight)
def hist_cmd(args: argparse.Namespace, config: Config) -> None: def hist_cmd(args: argparse.Namespace, config: Config) -> None:
+3 -3
View File
@@ -79,14 +79,14 @@ def create_parser() -> argparse.ArgumentParser:
hist_group = hist_cmd_parser.add_mutually_exclusive_group(required=True) hist_group = hist_cmd_parser.add_mutually_exclusive_group(required=True)
hist_group.add_argument('-p', '--print', help='Print the DB chat history', action='store_true') hist_group.add_argument('-p', '--print', help='Print the DB chat history', action='store_true')
hist_group.add_argument('-c', '--convert', help='Convert all message files to the given format [txt|yaml]', metavar='FORMAT') hist_group.add_argument('-c', '--convert', help='Convert all message files to the given format [txt|yaml]', metavar='FORMAT')
hist_cmd_parser.add_argument('-w', '--with-metadata', help="Print chat history with metadata (tags, filename, AI, etc.).", hist_cmd_parser.add_argument('-w', '--with-tags', help="Print chat history with tags.",
action='store_true')
hist_cmd_parser.add_argument('-W', '--with-files', help="Print chat history with filenames.",
action='store_true') action='store_true')
hist_cmd_parser.add_argument('-S', '--source-code-only', help='Only print embedded source code', hist_cmd_parser.add_argument('-S', '--source-code-only', help='Only print embedded source code',
action='store_true') action='store_true')
hist_cmd_parser.add_argument('-A', '--answer', help='Print only answers with given substring', metavar='SUBSTRING') hist_cmd_parser.add_argument('-A', '--answer', help='Print only answers with given substring', metavar='SUBSTRING')
hist_cmd_parser.add_argument('-Q', '--question', help='Print only questions with given substring', metavar='SUBSTRING') hist_cmd_parser.add_argument('-Q', '--question', help='Print only questions with given substring', metavar='SUBSTRING')
hist_cmd_parser.add_argument('-d', '--tight', help='Print without message separators', action='store_true')
hist_cmd_parser.add_argument('-P', '--no-paging', help='Print without paging', action='store_true')
# 'tags' command parser # 'tags' command parser
tags_cmd_parser = cmdparser.add_parser('tags', tags_cmd_parser = cmdparser.add_parser('tags',
+3 -4
View File
@@ -422,7 +422,7 @@ class Message():
except Exception: except Exception:
raise MessageError(f"'{file_path}' does not contain a valid message") raise MessageError(f"'{file_path}' does not contain a valid message")
def to_str(self, with_metadata: bool = False, source_code_only: bool = False) -> str: def to_str(self, with_tags: bool = False, with_file: bool = False, source_code_only: bool = False) -> str:
""" """
Return the current Message as a string. Return the current Message as a string.
""" """
@@ -432,11 +432,10 @@ class Message():
if self.answer: if self.answer:
output.extend(self.answer.source_code(include_delims=True)) output.extend(self.answer.source_code(include_delims=True))
return '\n'.join(output) if len(output) > 0 else '' return '\n'.join(output) if len(output) > 0 else ''
if with_metadata: if with_tags:
output.append(self.tags_str()) output.append(self.tags_str())
if with_file:
output.append('FILE: ' + str(self.file_path)) output.append('FILE: ' + str(self.file_path))
output.append('AI: ' + str(self.ai))
output.append('MODEL: ' + str(self.model))
output.append(Question.txt_header) output.append(Question.txt_header)
output.append(self.question) output.append(self.question)
if self.answer: if self.answer:
-1
View File
@@ -2,4 +2,3 @@ openai
PyYAML PyYAML
argcomplete argcomplete
pytest pytest
Jinja2
+6 -3
View File
@@ -2,8 +2,6 @@ from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh: with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read() long_description = fh.read()
with open("requirements.txt", "r") as fh:
install_requirements = [line.strip() for line in fh]
setup( setup(
name="ChatMastermind", name="ChatMastermind",
@@ -30,7 +28,12 @@ setup(
"Topic :: Utilities", "Topic :: Utilities",
"Topic :: Text Processing", "Topic :: Text Processing",
], ],
install_requires=install_requirements, install_requires=[
"openai",
"PyYAML",
"argcomplete",
"pytest",
],
python_requires=">=3.9", python_requires=">=3.9",
test_suite="tests", test_suite="tests",
entry_points={ entry_points={
+3 -11
View File
@@ -41,14 +41,10 @@ class TestChat(TestChatBase):
self.message1 = Message(Question('Question 1'), self.message1 = Message(Question('Question 1'),
Answer('Answer 1'), Answer('Answer 1'),
{Tag('atag1'), Tag('btag2')}, {Tag('atag1'), Tag('btag2')},
ai='FakeAI',
model='FakeModel',
file_path=pathlib.Path(f'0001{msg_suffix}')) file_path=pathlib.Path(f'0001{msg_suffix}'))
self.message2 = Message(Question('Question 2'), self.message2 = Message(Question('Question 2'),
Answer('Answer 2'), Answer('Answer 2'),
{Tag('btag2')}, {Tag('btag2')},
ai='FakeAI',
model='FakeModel',
file_path=pathlib.Path(f'0002{msg_suffix}')) file_path=pathlib.Path(f'0002{msg_suffix}'))
self.maxDiff = None self.maxDiff = None
@@ -147,7 +143,7 @@ class TestChat(TestChatBase):
@patch('sys.stdout', new_callable=StringIO) @patch('sys.stdout', new_callable=StringIO)
def test_print(self, mock_stdout: StringIO) -> None: def test_print(self, mock_stdout: StringIO) -> None:
self.chat.msg_add([self.message1, self.message2]) self.chat.msg_add([self.message1, self.message2])
self.chat.print(paged=False, tight=True) self.chat.print(paged=False)
expected_output = f"""{Question.txt_header} expected_output = f"""{Question.txt_header}
Question 1 Question 1
{Answer.txt_header} {Answer.txt_header}
@@ -160,21 +156,17 @@ Answer 2
self.assertEqual(mock_stdout.getvalue(), expected_output) self.assertEqual(mock_stdout.getvalue(), expected_output)
@patch('sys.stdout', new_callable=StringIO) @patch('sys.stdout', new_callable=StringIO)
def test_print_with_metadata(self, mock_stdout: StringIO) -> None: def test_print_with_tags_and_file(self, mock_stdout: StringIO) -> None:
self.chat.msg_add([self.message1, self.message2]) self.chat.msg_add([self.message1, self.message2])
self.chat.print(paged=False, with_metadata=True, tight=True) self.chat.print(paged=False, with_tags=True, with_files=True)
expected_output = f"""{TagLine.prefix} atag1 btag2 expected_output = f"""{TagLine.prefix} atag1 btag2
FILE: 0001{msg_suffix} FILE: 0001{msg_suffix}
AI: FakeAI
MODEL: FakeModel
{Question.txt_header} {Question.txt_header}
Question 1 Question 1
{Answer.txt_header} {Answer.txt_header}
Answer 1 Answer 1
{TagLine.prefix} btag2 {TagLine.prefix} btag2
FILE: 0002{msg_suffix} FILE: 0002{msg_suffix}
AI: FakeAI
MODEL: FakeModel
{Question.txt_header} {Question.txt_header}
Question 2 Question 2
{Answer.txt_header} {Answer.txt_header}
+4 -9
View File
@@ -4,7 +4,7 @@ import tempfile
import yaml import yaml
from pathlib import Path from pathlib import Path
from chatmastermind.message import Message, Question from chatmastermind.message import Message, Question
from chatmastermind.chat import ChatDB, ChatError from chatmastermind.chat import ChatDB
from chatmastermind.configuration import Config from chatmastermind.configuration import Config
from chatmastermind.commands.hist import convert_messages from chatmastermind.commands.hist import convert_messages
@@ -22,12 +22,13 @@ class TestConvertMessages(unittest.TestCase):
self.config = Config() self.config = Config()
self.config.cache = self.cache_dir.name self.config.cache = self.cache_dir.name
self.config.db = self.db_dir.name self.config.db = self.db_dir.name
self.args.convert = 'yaml'
# Prepare some messages # Prepare some messages
self.chat = ChatDB.from_dir(Path(self.cache_path), self.chat = ChatDB.from_dir(Path(self.cache_path),
Path(self.db_path)) Path(self.db_path))
self.messages = [Message(Question(f'Question {i}')) for i in range(0, 6)] self.messages = [Message(Question(f'Question {i}')) for i in range(1, 6)]
self.chat.db_write(self.messages[0:2]) self.chat.db_write(self.messages[0:2])
self.chat.cache_write(self.messages[2:]) self.chat.cache_write(self.messages[3:])
# Change some of the suffixes # Change some of the suffixes
assert self.messages[0].file_path assert self.messages[0].file_path
assert self.messages[1].file_path assert self.messages[1].file_path
@@ -39,7 +40,6 @@ class TestConvertMessages(unittest.TestCase):
self.cache_dir.cleanup() self.cache_dir.cleanup()
def test_convert_messages(self) -> None: def test_convert_messages(self) -> None:
self.args.convert = 'yaml'
convert_messages(self.args, self.config) convert_messages(self.args, self.config)
msgs = self.chat.msg_gather(loc='disk', glob='*.*') msgs = self.chat.msg_gather(loc='disk', glob='*.*')
# Check if the number of messages is the same as before # Check if the number of messages is the same as before
@@ -55,8 +55,3 @@ class TestConvertMessages(unittest.TestCase):
for m in msgs: for m in msgs:
with open(str(m.file_path), "r") as fd: with open(str(m.file_path), "r") as fd:
yaml.load(fd, Loader=yaml.FullLoader) yaml.load(fd, Loader=yaml.FullLoader)
def test_convert_messages_wrong_format(self) -> None:
self.args.convert = 'foo'
with self.assertRaises(ChatError):
convert_messages(self.args, self.config)
+1 -5
View File
@@ -856,8 +856,6 @@ class MessageToStrTestCase(unittest.TestCase):
def setUp(self) -> None: def setUp(self) -> None:
self.message = Message(Question('This is a question.'), self.message = Message(Question('This is a question.'),
Answer('This is an answer.'), Answer('This is an answer.'),
ai=('FakeAI'),
model=('FakeModel'),
tags={Tag('atag1'), Tag('btag2')}, tags={Tag('atag1'), Tag('btag2')},
file_path=pathlib.Path('/tmp/foo/bla')) file_path=pathlib.Path('/tmp/foo/bla'))
@@ -871,13 +869,11 @@ This is an answer."""
def test_to_str_with_tags_and_file(self) -> None: def test_to_str_with_tags_and_file(self) -> None:
expected_output = f"""{TagLine.prefix} atag1 btag2 expected_output = f"""{TagLine.prefix} atag1 btag2
FILE: /tmp/foo/bla FILE: /tmp/foo/bla
AI: FakeAI
MODEL: FakeModel
{Question.txt_header} {Question.txt_header}
This is a question. This is a question.
{Answer.txt_header} {Answer.txt_header}
This is an answer.""" This is an answer."""
self.assertEqual(self.message.to_str(with_metadata=True), expected_output) self.assertEqual(self.message.to_str(with_tags=True, with_file=True), expected_output)
class MessageRmFileTestCase(unittest.TestCase): class MessageRmFileTestCase(unittest.TestCase):