33import subprocess
44import sys
55import time
6+ from collections import deque
7+ from collections .abc import Generator , Sequence
68
79import pytest
810
911import fsspec
10- from fsspec .implementations .cached import CachingFileSystem
1112
1213
1314@pytest .fixture ()
@@ -27,16 +28,142 @@ def m():
2728 m .pseudo_dirs .append ("" )
2829
2930
30- @pytest .fixture
31+ class InstanceCacheInspector :
32+ """
33+ Helper class to inspect instance caches of filesystem classes in tests.
34+ """
35+
36+ @staticmethod
37+ def classes_from_refs (
38+ cls_reference : tuple [str | type [fsspec .AbstractFileSystem ], ...],
39+ / ,
40+ * ,
41+ empty_is_all : bool = True ,
42+ ) -> deque [type [fsspec .AbstractFileSystem ]]:
43+ """
44+ Convert class references (strings or types) to a deque of filesystem classes.
45+
46+ Parameters
47+ ----------
48+ cls_reference:
49+ Tuple of class references as strings or types.
50+ Supports fqns, protocol names, or the class types themselves.
51+ empty_is_all:
52+ If True and no classes are specified, include all imported filesystem classes.
53+
54+ Returns
55+ -------
56+ fs_classes:
57+ Deque of filesystem classes corresponding to the provided references.
58+ """
59+ classes : deque [type [fsspec .AbstractFileSystem ]] = deque ()
60+
61+ for ref in cls_reference :
62+ if isinstance (ref , str ):
63+ try :
64+ cls = fsspec .get_filesystem_class (ref )
65+ except ValueError :
66+ module_name , _ , class_name = ref .rpartition ("." )
67+ module = __import__ (module_name , fromlist = [class_name ])
68+ cls = getattr (module , class_name )
69+ classes .append (cls )
70+ else :
71+ classes .append (ref )
72+ if empty_is_all and not classes :
73+ classes .append (fsspec .spec .AbstractFileSystem )
74+ return classes
75+
76+ def clear (
77+ self ,
78+ * cls_reference : str | type [fsspec .AbstractFileSystem ],
79+ recursive : bool = True ,
80+ ) -> None :
81+ """
82+ Clear instance caches of specified filesystem classes.
83+ """
84+ classes = self .classes_from_refs (cls_reference )
85+ # Clear specified classes and optionally their subclasses
86+ while classes :
87+ cls = classes .popleft ()
88+ cls .clear_instance_cache ()
89+ if recursive :
90+ subclasses = cls .__subclasses__ ()
91+ classes .extend (subclasses )
92+
93+ def gather_counts (
94+ self ,
95+ * cls_reference : str | type [fsspec .AbstractFileSystem ],
96+ omit_zero : bool = True ,
97+ recursive : bool = True ,
98+ ) -> dict [str , int ]:
99+ """
100+ Gather counts of filesystem instances in the instance caches of all loaded classes.
101+
102+ Parameters
103+ ----------
104+ cls_reference:
105+ class references as strings or types.
106+ omit_zero:
107+ Whether to omit instance types with no cached instances.
108+ recursive:
109+ Whether to include subclasses of the specified classes.
110+ """
111+ out : dict [str , int ] = {}
112+ classes = self .classes_from_refs (cls_reference )
113+ while classes :
114+ cls = classes .popleft ()
115+ count = len (cls ._cache )
116+ # note: skip intermediate AbstractFileSystem subclasses
117+ # if they proxy the protocol attribute via a property.
118+ if isinstance (cls .protocol , (Sequence , str )):
119+ key = cls .protocol if isinstance (cls .protocol , str ) else cls .protocol [0 ]
120+ if count or not omit_zero :
121+ out [key ] = count
122+ if recursive :
123+ subclasses = cls .__subclasses__ ()
124+ classes .extend (subclasses )
125+ return out
126+
127+
128+ @pytest .fixture (scope = "function" , autouse = True )
129+ def instance_caches () -> Generator [InstanceCacheInspector , None , None ]:
130+ """
131+ Fixture to ensure empty filesystem instance caches before and after a test.
132+
133+ Used by default for all tests.
134+ Clears caches of all imported filesystem classes.
135+ Can be used to write test assertions about instance caches.
136+
137+ Usage:
138+
139+ def test_something(instance_caches):
140+ # Test code here
141+ fsspec.open("file://abc")
142+ fsspec.open("memory://foo/bar")
143+
144+ # Test assertion
145+ assert instance_caches.gather_counts() == {"file": 1, "memory": 1}
146+
147+ Returns
148+ -------
149+ instance_caches: An instance cache inspector for clearing and inspecting caches.
150+ """
151+ ic = InstanceCacheInspector ()
152+
153+ ic .clear ()
154+ try :
155+ yield ic
156+ finally :
157+ ic .clear ()
158+
159+
160+ @pytest .fixture (scope = "function" )
31161def ftp_writable (tmpdir ):
32162 """
33163 Fixture providing a writable FTP filesystem.
34164 """
35165 pytest .importorskip ("pyftpdlib" )
36- from fsspec .implementations .ftp import FTPFileSystem
37166
38- FTPFileSystem .clear_instance_cache () # remove lingering connections
39- CachingFileSystem .clear_instance_cache ()
40167 d = str (tmpdir )
41168 with open (os .path .join (d , "out" ), "wb" ) as f :
42169 f .write (b"hello" * 10000 )
0 commit comments