11"""Memory filesystem for snekbox."""
22from __future__ import annotations
33
4+ import glob
45import logging
6+ import time
57import warnings
68import weakref
79from collections .abc import Generator
@@ -125,6 +127,7 @@ def files(
125127 limit : int ,
126128 pattern : str = "**/*" ,
127129 exclude_files : dict [Path , float ] | None = None ,
130+ timeout : float | None = None ,
128131 ) -> Generator [FileAttachment , None , None ]:
129132 """
130133 Yields FileAttachments for files found in the output directory.
@@ -135,12 +138,18 @@ def files(
135138 exclude_files: A dict of Paths and last modified times.
136139 Files will be excluded if their last modified time
137140 is equal to the provided value.
141+ timeout: Maximum time in seconds for file parsing.
142+ Raises:
143+ TimeoutError: If file parsing exceeds timeout.
138144 """
145+ start_time = time .monotonic ()
139146 count = 0
140- for file in self .output .rglob (pattern ):
141- # Ignore hidden directories or files
142- if any (part .startswith ("." ) for part in file .parts ):
143- log .info (f"Skipping hidden path { file !s} " )
147+ files = glob .iglob (pattern , root_dir = str (self .output ), recursive = True , include_hidden = False )
148+ for file in (Path (self .output , f ) for f in files ):
149+ if timeout and (time .monotonic () - start_time ) > timeout :
150+ raise TimeoutError ("File parsing timeout exceeded in MemFS.files" )
151+
152+ if not file .is_file ():
144153 continue
145154
146155 if exclude_files and (orig_time := exclude_files .get (file )):
@@ -154,17 +163,17 @@ def files(
154163 log .info (f"Max attachments { limit } reached, skipping remaining files" )
155164 break
156165
157- if file .is_file ():
158- count += 1
159- log .info (f"Found valid file for upload { file .name !r} " )
160- yield FileAttachment .from_path (file , relative_to = self .output )
166+ count += 1
167+ log .info (f"Found valid file for upload { file .name !r} " )
168+ yield FileAttachment .from_path (file , relative_to = self .output )
161169
162170 def files_list (
163171 self ,
164172 limit : int ,
165173 pattern : str ,
166174 exclude_files : dict [Path , float ] | None = None ,
167175 preload_dict : bool = False ,
176+ timeout : float | None = None ,
168177 ) -> list [FileAttachment ]:
169178 """
170179 Return a sorted list of file paths within the output directory.
@@ -176,15 +185,21 @@ def files_list(
176185 Files will be excluded if their last modified time
177186 is equal to the provided value.
178187 preload_dict: Whether to preload as_dict property data.
188+ timeout: Maximum time in seconds for file parsing.
179189 Returns:
180190 List of FileAttachments sorted lexically by path name.
191+ Raises:
192+ TimeoutError: If file parsing exceeds timeout.
181193 """
194+ start_time = time .monotonic ()
182195 res = sorted (
183196 self .files (limit = limit , pattern = pattern , exclude_files = exclude_files ),
184197 key = lambda f : f .path ,
185198 )
186199 if preload_dict :
187200 for file in res :
201+ if timeout and (time .monotonic () - start_time ) > timeout :
202+ raise TimeoutError ("File parsing timeout exceeded in MemFS.files_list" )
188203 # Loads the cached property as attribute
189204 _ = file .as_dict
190205 return res
0 commit comments