1+ import os .path
2+
13import pickle
2- import numpy as np
34from pathlib import Path
5+ from ..streaming .utils import dic_merger
6+ import pickletools
7+ import gzip
8+ from multiprocessing import Pool
9+
10+
11+ def _load_and_compress (origin_file , target_file = None ):
12+ data = load (origin_file )
13+ if not target_file :
14+ target_file = origin_file .split ("." )[:- 1 ][0 ] + ".bio.gzip"
15+ if Path (target_file ).suffix != ".gzip" :
16+ target_file = target_file + ".gzip"
17+ f = gzip .open (target_file , "wb" )
18+ data_pick = pickletools .optimize (pickle .dumps (data , pickle .HIGHEST_PROTOCOL ))
19+ f .write (data_pick )
20+ f .close ()
21+
22+
23+ def compress (origin_file , target_file = None , multi_proc = False ):
24+ if target_file and len (target_file ) != len (origin_file ):
25+ raise ValueError ("The number of target files must be equal to the number of origin files." )
26+ if multi_proc :
27+ pool = Pool (processes = os .cpu_count ())
28+ pool .map (_load_and_compress , origin_file )
29+ else :
30+ for i in range (len (origin_file )):
31+ target_file_tmp = target_file [i ] if target_file else None
32+ _load_and_compress (origin_file [i ], target_file_tmp )
33+
34+
35+ def _safe_rename_file (file_name ) -> str :
36+ """This function checks if the file extension is an integer.
437
38+ Parameters
39+ ----------
40+ file_name : str
41+ The name to the file.
42+
43+ Returns
44+ -------
45+ str
46+ new name of the file.
47+ """
48+ i = 0
49+ while file_name [- (i + 1 )].isdigit ():
50+ i += 1
51+ old_value = int (file_name [- i :]) if i > 0 else None
52+ if old_value :
53+ if file_name [- (i + 1 )] == "_" or file_name [- (i + 1 )] == "-" :
54+ i += 1
55+ return file_name [:- i ] + "_" + str (old_value + 1 )
56+ else :
57+ return file_name + "_1"
558
6- def save (data_dict , data_path ):
59+
60+ def save (data_dict , data_path , add_data = False , safe = True , compress = False ):
761 """This function adds data to a pickle file. It not open the file, but appends the data to the end, so it's fast.
862
963 Parameters
@@ -12,18 +66,44 @@ def save(data_dict, data_path):
1266 The data to be added to the file.
1367 data_path : str
1468 The path to the file. The file must exist.
69+ add_data : bool, optional
70+ If True, the data are added to the file. If False, the file is overwritten. The default is False.
71+ safe : bool, optional
72+ If True, the data are saved in a new file. The default is False.
73+ compress : bool, optional
74+ If True, the data are compressed. The default is False.
1575 """
16- if Path (data_path ).suffix != ".bio" :
17- if Path (data_path ).suffix == "" :
76+ data_path_object = Path (data_path )
77+ if data_path_object .suffix != ".bio" :
78+ if data_path_object .suffix == "" :
1879 data_path += ".bio"
1980 else :
2081 raise ValueError ("The file must be a .bio file." )
21- with open (data_path , "ab" ) as outf :
22- pickle .dump (data_dict , outf , pickle .HIGHEST_PROTOCOL )
82+ if not add_data :
83+ if safe and os .path .isfile (data_path ):
84+ file_name = _safe_rename_file (data_path )
85+ data_path = data_path_object .parent / (file_name + data_path_object .suffix )
86+ print (
87+ f"The file { data_path_object .name } already exists. The data will be saved in { data_path .name } ."
88+ f" To avoid this message, remove the safe option."
89+ )
90+ if not safe and os .path .isfile (data_path ):
91+ os .remove (data_path )
92+
93+ if compress :
94+ if add_data :
95+ raise ValueError ("The data cannot be added to a compressed file." )
96+ data_path = data_path + ".gzip"
97+ f = gzip .open (data_path , "wb" )
98+ data_pick = pickletools .optimize (pickle .dumps (data_dict , pickle .HIGHEST_PROTOCOL ))
99+ f .write (data_pick )
100+ f .close ()
101+ else :
102+ with open (data_path , "ab" ) as outf :
103+ pickle .dump (data_dict , outf , pickle .HIGHEST_PROTOCOL )
23104
24105
25- # TODO add dict merger
26- def load (filename , number_of_line = None ):
106+ def load_old (filename , number_of_line = None , merge = True ):
27107 """This function reads data from a pickle file to concatenate them into one dictionary.
28108
29109 Parameters
@@ -32,39 +112,115 @@ def load(filename, number_of_line=None):
32112 The path to the file.
33113 number_of_line : int
34114 The number of lines to read. If None, all lines are read.
115+ merge : bool
116+ If True, the data are merged into one dictionary. If False, the data are returned as a list of dictionaries.
35117
36118 Returns
37119 -------
38120 data : dict
39121 The data read from the file.
40122
41123 """
42- if Path (filename ).suffix != ".bio" :
43- raise ValueError ("The file must be a .bio file." )
44- data = {}
124+ with_gzip = False
125+
126+ if Path (filename ).suffix == ".bio" :
127+ with_gzip = False
128+ elif Path (filename ).suffix == ".gzip" :
129+ with_gzip = True
130+ # else:
131+ # raise ValueError("The file must be a .bio or a .bio.gzip file.")
132+ data = None if merge else []
45133 limit = 2 if not number_of_line else number_of_line
46- with open (filename , "rb" ) as file :
134+ if with_gzip :
135+ with gzip .open (filename , "rb" ) as file :
136+ count = 0
137+ while count < limit :
138+ try :
139+ data_tmp = pickle .load (file )
140+ if not merge :
141+ data .append (data_tmp )
142+ else :
143+ if not data :
144+ data = data_tmp
145+ else :
146+ data = dic_merger (data , data_tmp )
147+ if number_of_line :
148+ count += 1
149+ else :
150+ count = 1
151+ except EOFError :
152+ break
153+ else :
154+ with open (filename , "rb" ) as file :
155+ count = 0
156+ while count < limit :
157+ try :
158+ data_tmp = pickle .load (file )
159+ if not merge :
160+ data .append (data_tmp )
161+ else :
162+ if not data :
163+ data = data_tmp
164+ else :
165+ data = dic_merger (data , data_tmp )
166+ if number_of_line :
167+ count += 1
168+ else :
169+ count = 1
170+ except EOFError :
171+ break
172+ return data
173+
174+
175+ def load (filename , number_of_line = None , merge = True ):
176+ """This function reads data from a pickle file to concatenate them into one dictionary.
177+
178+ Parameters
179+ ----------
180+ filename : str
181+ The path to the file.
182+ number_of_line : int
183+ The number of lines to read. If None, all lines are read.
184+ merge : bool
185+ If True, the data are merged into one dictionary. If False, the data are returned as a list of dictionaries.
186+
187+ Returns
188+ -------
189+ data : dict
190+ The data read from the file.
191+
192+ """
193+ if not Path (filename ).is_file ():
194+ raise FileNotFoundError (f"The file { filename } does not exist." )
195+
196+ with_gzip = filename .endswith (".gzip" )
197+ data = None if merge else []
198+ limit = number_of_line if number_of_line else float ("inf" )
199+
200+ try :
201+ return _read_all_lines (filename , limit , data , with_gzip , merge )
202+ except Exception as e :
203+ raise RuntimeError (f"An error occurred while loading the file: { e } " )
204+
205+
206+ def _read_all_lines (filename , limit = float ("inf" ), data = (), with_gzip = False , merge = True ):
207+ file_open = gzip .open if with_gzip else open
208+ with file_open (filename , "rb" ) as file :
47209 count = 0
48210 while count < limit :
49211 try :
50212 data_tmp = pickle .load (file )
51- for key in data_tmp .keys ():
52- if key in data .keys ():
53- if isinstance (data [key ], list ) is True :
54- data [key ].append (data_tmp [key ])
55- else :
56- data [key ] = np .append (data [key ], data_tmp [key ], axis = len (data [key ].shape ) - 1 )
57- else :
58- if isinstance (data_tmp [key ], (int , float , str , dict )) is True :
59- data [key ] = [data_tmp [key ]]
60- elif isinstance (data_tmp [key ], list ) is True :
61- data [key ] = [data_tmp [key ]]
62- else :
63- data [key ] = data_tmp [key ]
64- if number_of_line :
65- count += 1
213+ if not merge :
214+ data .append (data_tmp )
215+ else :
216+ data = dic_merger (data , data_tmp ) if data else data_tmp
217+ count += 1
218+ except Exception as e :
219+ if str (e ) == "Ran out of input" :
220+ # print(f"{count} lines read from the file")
221+ pass
66222 else :
67- count = 1
68- except EOFError :
223+ print (f"An error occurred while reading the file: { e } at line { count } " )
69224 break
225+
70226 return data
0 commit comments