Compare commits

..

8 Commits

3 changed files with 21 additions and 62 deletions

View File

@ -60,17 +60,13 @@ class Chat:
@dataclass
class ChatDir(Chat):
"""
A 'Chat' class that is bound to a given directory structure. Supports reading
and writing messages from / to that structure. Such a structure consists of
two directories: a 'cache directory', where all messages are temporarily
stored, and a 'DB' directory, where selected messages can be stored
persistently.
A Chat class that is bound to a given directory. Supports reading
and writing messages from / to that directory.
"""
default_file_suffix: ClassVar[str] = '.txt'
cache_path: pathlib.Path
db_path: pathlib.Path
directory: pathlib.Path
# a MessageFilter that all messages must match (if given)
mfilter: Optional[MessageFilter] = None
file_suffix: str = default_file_suffix
@ -79,24 +75,17 @@ class ChatDir(Chat):
@classmethod
def from_dir(cls: Type[ChatDirInst],
cache_path: pathlib.Path,
db_path: pathlib.Path,
path: pathlib.Path,
glob: Optional[str] = None,
mfilter: Optional[MessageFilter] = None) -> ChatDirInst:
"""
Create a 'ChatDir' instance from the given directory structure.
Reads all messages from 'db_path' into the local message list.
Parameters:
* 'cache_path': path to the directory for temporary messages
* 'db_path': path to the directory for persistent messages
* 'glob' fs specified, files will be filtered using 'path.glob()',
otherwise it uses 'path.iterdir()'.
* 'mfilter': use with 'Message.from_file()' to filter messages
when reading them.
Create a ChatDir instance from the given directory. If 'glob' is specified,
files will be filtered using 'path.glob()', otherwise it uses 'path.iterdir()'.
Messages are created using 'Message.from_file()' and the optional MessageFilter.
"""
messages: list[Message] = []
message_files: set[str] = set()
file_iter = db_path.glob(glob) if glob else db_path.iterdir()
file_iter = path.glob(glob) if glob else path.iterdir()
for file_path in sorted(file_iter):
if file_path.is_file():
try:
@ -106,12 +95,11 @@ class ChatDir(Chat):
message_files.add(file_path.name)
except MessageError as e:
print(f"Error processing message in '{file_path}': {str(e)}")
return cls(messages, cache_path, db_path, mfilter, cls.default_file_suffix, message_files)
return cls(messages, path, mfilter, cls.default_file_suffix, message_files)
@classmethod
def from_messages(cls: Type[ChatDirInst],
cache_path: pathlib.Path,
db_path: pathlib.Path,
path: pathlib.Path,
messages: list[Message],
mfilter: Optional[MessageFilter]) -> ChatDirInst:
"""
@ -120,10 +108,10 @@ class ChatDir(Chat):
in order to synchronize the messages. 'update()' is not
supported until after the first 'dump()'.
"""
return cls(messages, cache_path, db_path, mfilter)
return cls(messages, path, mfilter)
def get_next_fid(self) -> int:
next_fname = self.db_path / '.next'
next_fname = self.directory / '.next'
try:
with open(next_fname, 'r') as f:
return int(f.read()) + 1
@ -131,16 +119,18 @@ class ChatDir(Chat):
return 1
def set_next_fid(self, fid: int) -> None:
next_fname = self.db_path / '.next'
next_fname = self.directory / '.next'
with open(next_fname, 'w') as f:
f.write(f'{fid}')
def dump(self, to_db: bool = False, force_all: bool = False) -> None:
def dump(self, force_all: bool = False) -> None:
"""
Writes all messages to the 'cache_path' or 'db_path'. If a message has no file_path,
it will create a new one. By default, only messages that have not been written
(or read) before will be dumped. Use 'force_all' to force writing all message files.
Writes all messages to the bound directory. If a message has no file_path,
it will create a new one. By default, only messages that have not been
written (or read) before will be dumped. Use 'force_all' to force writing
all message files.
"""
# FIXME: write to 'db' subfolder or given folder
for message in self.messages:
# skip messages that we have already written (or read)
if message.file_path and message.file_path in self.message_files and not force_all:
@ -148,7 +138,6 @@ class ChatDir(Chat):
file_path = message.file_path
if not file_path:
fid = self.get_next_fid()
fname = f"{fid:04d}{self.file_suffix}"
file_path = self.db_path / fname if to_db else self.cache_path / fname
file_path = self.directory / f"{fid:04d}{self.file_suffix}"
self.set_next_fid(fid)
message.to_file(file_path)

View File

@ -187,7 +187,7 @@ class Message():
and a file path.
"""
question: Question
answer: Optional[Answer] = None
answer: Optional[Answer] = None # FIXME: support multiple answers
tags: Optional[set[Tag]] = None
ai: Optional[str] = None
model: Optional[str] = None
@ -409,15 +409,5 @@ class Message():
return False
return True
def file_id(self) -> str:
"""
Returns an ID that is unique within the directory of this message.
Currently this is simply the file name.
"""
if self.file_path:
return self.file_path.name
else:
raise MessageError("Can't create file ID without a file path")
def as_dict(self) -> dict[str, Any]:
return asdict(self)

View File

@ -575,23 +575,3 @@ This is an answer.
def test_tags_from_file_yaml(self) -> None:
tags = Message.tags_from_file(self.file_path_yaml)
self.assertSetEqual(tags, {Tag('tag1'), Tag('tag2')})
class MessageFileIDTxtTestCase(CmmTestCase):
def setUp(self) -> None:
self.file = tempfile.NamedTemporaryFile(delete=False, suffix='.txt')
self.file_path = pathlib.Path(self.file.name)
self.message = Message(Question('This is a question.'),
file_path=self.file_path)
self.message_no_file_path = Message(Question('This is a question.'))
def tearDown(self) -> None:
self.file.close()
self.file_path.unlink()
def test_file_id_txt(self) -> None:
self.assertEqual(self.message.file_id(), self.file_path.name)
def test_file_id_txt_exception(self) -> None:
with self.assertRaises(MessageError):
self.message_no_file_path.file_id()