1- import os
21import io
3- import glob
42from pathlib import Path
3+ import glob
4+ from dataclasses import dataclass
5+ from typing import List , Generator , Tuple
6+
57from chatterbot .exceptions import OptionalDependencyImportError
68
9+ # Try to import ChatterBot corpus data directory
710try :
811 from chatterbot_corpus .corpus import DATA_DIRECTORY
912except (ImportError , ModuleNotFoundError ):
10- # Default to the home directory of the current user
11- DATA_DIRECTORY = os .path .join (
12- Path .home (),
13- 'chatterbot_corpus' ,
14- 'data'
15- )
13+ # Default to home directory
14+ DATA_DIRECTORY = Path .home () / 'chatterbot_corpus' / 'data'
1615
16+ # Default corpus file extensions
17+ CORPUS_EXTENSIONS = ['yml' , 'yaml' , 'json' ]
1718
18- CORPUS_EXTENSION = 'yml'
19+ # Simple cache for loaded corpus files
20+ _corpus_cache = {}
1921
2022
21- def get_file_path (dotted_path , extension = 'json' ) -> str :
23+ @dataclass
24+ class CorpusData :
25+ conversations : List [List [str ]]
26+ categories : List [str ]
27+ file_path : str
28+
29+
30+ def get_file_path (dotted_path : str , extensions : List [str ] = CORPUS_EXTENSIONS ) -> Path :
2231 """
23- Reads a dotted file path and returns the file path.
32+ Convert a dotted path or filesystem path into an actual file path.
33+ Raises FileNotFoundError if the file does not exist.
2434 """
25- # If the operating system's file path seperator character is in the string
26- if os .sep in dotted_path or '/' in dotted_path :
27- # Assume the path is a valid file path
28- return dotted_path
35+ path = Path (dotted_path )
36+
37+ # If path already exists, return it
38+ if path .exists ():
39+ return path
2940
41+ # Split dotted path
3042 parts = dotted_path .split ('.' )
3143 if parts [0 ] == 'chatterbot' :
32- parts .pop (0 )
33- parts [0 ] = DATA_DIRECTORY
44+ parts [0 ] = str (DATA_DIRECTORY )
45+
46+ base_path = Path (* parts )
3447
35- corpus_path = os .path .join (* parts )
48+ # Check for file existence with supported extensions
49+ for ext in extensions :
50+ candidate = base_path .with_suffix (f'.{ ext } ' )
51+ if candidate .exists ():
52+ return candidate
3653
37- path_with_extension = '{}.{}' . format ( corpus_path , extension )
38- if os . path . exists ( path_with_extension ):
39- corpus_path = path_with_extension
54+ # If directory exists, return it
55+ if base_path . is_dir ( ):
56+ return base_path
4057
41- return corpus_path
58+ raise FileNotFoundError ( f"Corpus file or directory not found for: { dotted_path } " )
4259
4360
44- def read_corpus (file_name ) -> dict :
61+ def read_corpus (file_path : Path ) -> dict :
4562 """
46- Read and return the data from a corpus json file.
63+ Read a YAML or JSON corpus file and return its contents.
64+ Caches results for repeated access.
4765 """
66+ if file_path in _corpus_cache :
67+ return _corpus_cache [file_path ]
68+
4869 try :
4970 import yaml
5071 except ImportError :
@@ -55,37 +76,49 @@ def read_corpus(file_name) -> dict:
5576 )
5677 raise OptionalDependencyImportError (message )
5778
58- with io .open (file_name , encoding = 'utf-8' ) as data_file :
59- return yaml .safe_load (data_file )
79+ try :
80+ with io .open (file_path , encoding = 'utf-8' ) as f :
81+ data = yaml .safe_load (f )
82+ except Exception as e :
83+ raise RuntimeError (f"Failed to read corpus file { file_path } : { e } " ) from e
84+
85+ if not isinstance (data , dict ):
86+ raise ValueError (f"Corpus file { file_path } did not return a dictionary." )
87+
88+ _corpus_cache [file_path ] = data
89+ return data
6090
6191
62- def list_corpus_files (dotted_path ) -> list [ str ]:
92+ def list_corpus_files (dotted_path : str ) -> List [ Path ]:
6393 """
64- Return a list of file paths to each data file in the specified corpus.
94+ Return a sorted list of all corpus files (with supported extensions)
95+ in the given dotted path or directory.
6596 """
66- corpus_path = get_file_path (dotted_path , extension = CORPUS_EXTENSION )
67- paths = []
97+ path = get_file_path (dotted_path )
98+ files : List [ Path ] = []
6899
69- if os .path .isdir (corpus_path ):
70- paths = glob .glob (corpus_path + '/**/*.' + CORPUS_EXTENSION , recursive = True )
100+ if path .is_dir ():
101+ for ext in CORPUS_EXTENSIONS :
102+ files .extend (path .rglob (f'*.{ ext } ' ))
71103 else :
72- paths .append (corpus_path )
104+ files .append (path )
73105
74- paths .sort ()
75- return paths
106+ return sorted (files )
76107
77108
78- def load_corpus (* data_file_paths ) :
109+ def load_corpus (* data_file_paths : str ) -> Generator [ CorpusData , None , None ] :
79110 """
80- Return the data contained within a specified corpus.
111+ Yield CorpusData objects for each specified corpus file .
81112 """
82- for file_path in data_file_paths :
83- corpus = []
84- corpus_data = read_corpus (file_path )
85-
86- conversations = corpus_data .get ('conversations' , [])
87- corpus .extend (conversations )
88-
89- categories = corpus_data .get ('categories' , [])
90-
91- yield corpus , categories , file_path
113+ for file_path_str in data_file_paths :
114+ path = get_file_path (file_path_str )
115+ if path .is_dir ():
116+ files = list_corpus_files (path )
117+ else :
118+ files = [path ]
119+
120+ for file in files :
121+ corpus_data = read_corpus (file )
122+ conversations = corpus_data .get ('conversations' , [])
123+ categories = corpus_data .get ('categories' , [])
124+ yield CorpusData (conversations , categories , str (file ))
0 commit comments