fsspec 2025.5.0__py3-none-any.whl → 2025.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fsspec/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2025.5.0'
21
- __version_tuple__ = version_tuple = (2025, 5, 0)
20
+ __version__ = version = '2025.5.1'
21
+ __version_tuple__ = version_tuple = (2025, 5, 1)
@@ -0,0 +1,232 @@
1
+ import requests
2
+
3
+ from ..spec import AbstractFileSystem
4
+ from ..utils import infer_storage_options
5
+ from .memory import MemoryFile
6
+
7
+
8
+ class GistFileSystem(AbstractFileSystem):
9
+ """
10
+ Interface to files in a single GitHub Gist.
11
+
12
+ Provides read-only access to a gist's files. Gists do not contain
13
+ subdirectories, so file listing is straightforward.
14
+
15
+ Parameters
16
+ ----------
17
+ gist_id : str
18
+ The ID of the gist you want to access (the long hex value from the URL).
19
+ filenames : list[str] (optional)
20
+ If provided, only make a file system representing these files, and do not fetch
21
+ the list of all files for this gist.
22
+ sha : str (optional)
23
+ If provided, fetch a particular revision of the gist. If omitted,
24
+ the latest revision is used.
25
+ username : str (optional)
26
+ GitHub username for authentication (required if token is given).
27
+ token : str (optional)
28
+ GitHub personal access token (required if username is given).
29
+ timeout : (float, float) or float, optional
30
+ Connect and read timeouts for requests (default 60s each).
31
+ kwargs : dict
32
+ Stored on `self.request_kw` and passed to `requests.get` when fetching Gist
33
+ metadata or reading ("opening") a file.
34
+ """
35
+
36
+ protocol = "gist"
37
+ gist_url = "https://api.github.com/gists/{gist_id}"
38
+ gist_rev_url = "https://api.github.com/gists/{gist_id}/{sha}"
39
+
40
+ def __init__(
41
+ self,
42
+ gist_id,
43
+ filenames=None,
44
+ sha=None,
45
+ username=None,
46
+ token=None,
47
+ timeout=None,
48
+ **kwargs,
49
+ ):
50
+ super().__init__()
51
+ self.gist_id = gist_id
52
+ self.filenames = filenames
53
+ self.sha = sha # revision of the gist (optional)
54
+ if (username is None) ^ (token is None):
55
+ # Both or neither must be set
56
+ if username or token:
57
+ raise ValueError("Auth requires both username and token, or neither.")
58
+ self.username = username
59
+ self.token = token
60
+ self.request_kw = kwargs
61
+ # Default timeouts to 60s connect/read if none provided
62
+ self.timeout = timeout if timeout is not None else (60, 60)
63
+
64
+ # We use a single-level "directory" cache, because a gist is essentially flat
65
+ self.dircache[""] = self._fetch_file_list()
66
+
67
+ @property
68
+ def kw(self):
69
+ """Auth parameters passed to 'requests' if we have username/token."""
70
+ if self.username is not None and self.token is not None:
71
+ return {"auth": (self.username, self.token), **self.request_kw}
72
+ return self.request_kw
73
+
74
+ def _fetch_gist_metadata(self):
75
+ """
76
+ Fetch the JSON metadata for this gist (possibly for a specific revision).
77
+ """
78
+ if self.sha:
79
+ url = self.gist_rev_url.format(gist_id=self.gist_id, sha=self.sha)
80
+ else:
81
+ url = self.gist_url.format(gist_id=self.gist_id)
82
+
83
+ r = requests.get(url, timeout=self.timeout, **self.kw)
84
+ if r.status_code == 404:
85
+ raise FileNotFoundError(
86
+ f"Gist not found: {self.gist_id}@{self.sha or 'latest'}"
87
+ )
88
+ r.raise_for_status()
89
+ return r.json()
90
+
91
+ def _fetch_file_list(self):
92
+ """
93
+ Returns a list of dicts describing each file in the gist. These get stored
94
+ in self.dircache[""].
95
+ """
96
+ meta = self._fetch_gist_metadata()
97
+ if self.filenames:
98
+ available_files = meta.get("files", {})
99
+ files = {}
100
+ for fn in self.filenames:
101
+ if fn not in available_files:
102
+ raise FileNotFoundError(fn)
103
+ files[fn] = available_files[fn]
104
+ else:
105
+ files = meta.get("files", {})
106
+
107
+ out = []
108
+ for fname, finfo in files.items():
109
+ if finfo is None:
110
+ # Occasionally GitHub returns a file entry with null if it was deleted
111
+ continue
112
+ # Build a directory entry
113
+ out.append(
114
+ {
115
+ "name": fname, # file's name
116
+ "type": "file", # gists have no subdirectories
117
+ "size": finfo.get("size", 0), # file size in bytes
118
+ "raw_url": finfo.get("raw_url"),
119
+ }
120
+ )
121
+ return out
122
+
123
+ @classmethod
124
+ def _strip_protocol(cls, path):
125
+ """
126
+ Remove 'gist://' from the path, if present.
127
+ """
128
+ # The default infer_storage_options can handle gist://username:token@id/file
129
+ # or gist://id/file, but let's ensure we handle a normal usage too.
130
+ # We'll just strip the protocol prefix if it exists.
131
+ path = infer_storage_options(path).get("path", path)
132
+ return path.lstrip("/")
133
+
134
+ @staticmethod
135
+ def _get_kwargs_from_urls(path):
136
+ """
137
+ Parse 'gist://' style URLs into GistFileSystem constructor kwargs.
138
+ For example:
139
+ gist://:TOKEN@<gist_id>/file.txt
140
+ gist://username:TOKEN@<gist_id>/file.txt
141
+ """
142
+ so = infer_storage_options(path)
143
+ out = {}
144
+ if "username" in so and so["username"]:
145
+ out["username"] = so["username"]
146
+ if "password" in so and so["password"]:
147
+ out["token"] = so["password"]
148
+ if "host" in so and so["host"]:
149
+ # We interpret 'host' as the gist ID
150
+ out["gist_id"] = so["host"]
151
+
152
+ # Extract SHA and filename from path
153
+ if "path" in so and so["path"]:
154
+ path_parts = so["path"].rsplit("/", 2)[-2:]
155
+ if len(path_parts) == 2:
156
+ if path_parts[0]: # SHA present
157
+ out["sha"] = path_parts[0]
158
+ if path_parts[1]: # filename also present
159
+ out["filenames"] = [path_parts[1]]
160
+
161
+ return out
162
+
163
+ def ls(self, path="", detail=False, **kwargs):
164
+ """
165
+ List files in the gist. Gists are single-level, so any 'path' is basically
166
+ the filename, or empty for all files.
167
+
168
+ Parameters
169
+ ----------
170
+ path : str, optional
171
+ The filename to list. If empty, returns all files in the gist.
172
+ detail : bool, default False
173
+ If True, return a list of dicts; if False, return a list of filenames.
174
+ """
175
+ path = self._strip_protocol(path or "")
176
+ # If path is empty, return all
177
+ if path == "":
178
+ results = self.dircache[""]
179
+ else:
180
+ # We want just the single file with this name
181
+ all_files = self.dircache[""]
182
+ results = [f for f in all_files if f["name"] == path]
183
+ if not results:
184
+ raise FileNotFoundError(path)
185
+ if detail:
186
+ return results
187
+ else:
188
+ return sorted(f["name"] for f in results)
189
+
190
+ def _open(self, path, mode="rb", block_size=None, **kwargs):
191
+ """
192
+ Read a single file from the gist.
193
+ """
194
+ if mode != "rb":
195
+ raise NotImplementedError("GitHub Gist FS is read-only (no write).")
196
+
197
+ path = self._strip_protocol(path)
198
+ # Find the file entry in our dircache
199
+ matches = [f for f in self.dircache[""] if f["name"] == path]
200
+ if not matches:
201
+ raise FileNotFoundError(path)
202
+ finfo = matches[0]
203
+
204
+ raw_url = finfo.get("raw_url")
205
+ if not raw_url:
206
+ raise FileNotFoundError(f"No raw_url for file: {path}")
207
+
208
+ r = requests.get(raw_url, timeout=self.timeout, **self.kw)
209
+ if r.status_code == 404:
210
+ raise FileNotFoundError(path)
211
+ r.raise_for_status()
212
+ return MemoryFile(path, None, r.content)
213
+
214
+ def cat(self, path, recursive=False, on_error="raise", **kwargs):
215
+ """
216
+ Return {path: contents} for the given file or files. If 'recursive' is True,
217
+ and path is empty, returns all files in the gist.
218
+ """
219
+ paths = self.expand_path(path, recursive=recursive)
220
+ out = {}
221
+ for p in paths:
222
+ try:
223
+ with self.open(p, "rb") as f:
224
+ out[p] = f.read()
225
+ except FileNotFoundError as e:
226
+ if on_error == "raise":
227
+ raise e
228
+ elif on_error == "omit":
229
+ pass # skip
230
+ else:
231
+ out[p] = e
232
+ return out
@@ -7,8 +7,6 @@ from ..spec import AbstractFileSystem
7
7
  from ..utils import infer_storage_options
8
8
  from .memory import MemoryFile
9
9
 
10
- # TODO: add GIST backend, would be very similar
11
-
12
10
 
13
11
  class GithubFileSystem(AbstractFileSystem):
14
12
  """Interface to files in github
fsspec/registry.py CHANGED
@@ -122,6 +122,10 @@ known_implementations = {
122
122
  "err": "Please install gdrivefs for access to Google Drive",
123
123
  },
124
124
  "generic": {"class": "fsspec.generic.GenericFileSystem"},
125
+ "gist": {
126
+ "class": "fsspec.implementations.gist.GistFileSystem",
127
+ "err": "Install the requests package to use the gist FS",
128
+ },
125
129
  "git": {
126
130
  "class": "fsspec.implementations.git.GitFileSystem",
127
131
  "err": "Install pygit2 to browse local git repos",
fsspec/spec.py CHANGED
@@ -557,9 +557,9 @@ class AbstractFileSystem(metaclass=_Cached):
557
557
  path: str
558
558
  The glob pattern to match against
559
559
  maxdepth: int or None
560
- Maximum depth for '**' patterns. Applied on the first '**' found.
560
+ Maximum depth for ``'**'`` patterns. Applied on the first ``'**'`` found.
561
561
  Must be at least 1 if provided.
562
- **kwargs:
562
+ kwargs:
563
563
  Additional arguments passed to ``find`` (e.g., detail=True)
564
564
 
565
565
  Returns
@@ -570,7 +570,7 @@ class AbstractFileSystem(metaclass=_Cached):
570
570
  -----
571
571
  Supported patterns:
572
572
  - '*': Matches any sequence of characters within a single directory level
573
- - '**': Matches any number of directory levels (must be an entire path component)
573
+ - ``'**'``: Matches any number of directory levels (must be an entire path component)
574
574
  - '?': Matches exactly one character
575
575
  - '[abc]': Matches any character in the set
576
576
  - '[a-z]': Matches any character in the range
@@ -584,7 +584,7 @@ class AbstractFileSystem(metaclass=_Cached):
584
584
  - Special characters in character classes are escaped properly
585
585
 
586
586
  Limitations:
587
- - '**' must be a complete path component (e.g., 'a/**/b', not 'a**b')
587
+ - ``'**'`` must be a complete path component (e.g., ``'a/**/b'``, not ``'a**b'``)
588
588
  - No brace expansion ('{a,b}.txt')
589
589
  - No extended glob patterns ('+(pattern)', '!(pattern)')
590
590
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fsspec
3
- Version: 2025.5.0
3
+ Version: 2025.5.1
4
4
  Summary: File-system specification
5
5
  Project-URL: Changelog, https://filesystem-spec.readthedocs.io/en/latest/changelog.html
6
6
  Project-URL: Documentation, https://filesystem-spec.readthedocs.io/en/latest/
@@ -1,5 +1,5 @@
1
1
  fsspec/__init__.py,sha256=L7qwNBU1iMNQd8Of87HYSNFT9gWlNMSESaJC8fY0AaQ,2053
2
- fsspec/_version.py,sha256=d5C2cu2VxxYpS-PNB1YmoIJOnoUQMsDSy-TKkHnh768,517
2
+ fsspec/_version.py,sha256=wECC04X-mZMQuTtKrW1mQDmwI4m9ylB6qWwnMihRB6I,517
3
3
  fsspec/archive.py,sha256=vM6t_lgV6lBWbBYwpm3S4ofBQFQxUPr5KkDQrrQcQro,2411
4
4
  fsspec/asyn.py,sha256=VJ2jBdYgUjV4_dETpKeCp2wF1XHAdeUET95d2HqNZck,36776
5
5
  fsspec/caching.py,sha256=n_SbdT-l92Kqo3e1BQgef0uEWJD0raP5-Qd8Ewp8CHY,34292
@@ -16,8 +16,8 @@ fsspec/gui.py,sha256=xBnHL2-r0LVwhDAtnHoPpXts7jd4Z32peawCJiI-7lI,13975
16
16
  fsspec/json.py,sha256=65sQ0Y7mTj33u_Y4IId5up4abQ3bAel4E4QzbKMiQSg,3826
17
17
  fsspec/mapping.py,sha256=m2ndB_gtRBXYmNJg0Ie1-BVR75TFleHmIQBzC-yWhjU,8343
18
18
  fsspec/parquet.py,sha256=6ibAmG527L5JNFS0VO8BDNlxHdA3bVYqdByeiFgpUVM,19448
19
- fsspec/registry.py,sha256=9C8Ru2CU_d495yZBrXcP7H6fF_WfAqtfwx7ZTPUnGwE,11743
20
- fsspec/spec.py,sha256=xD7fd3nGIQPPljic-sS0ckWoO9jIqsr613UjHf8sOtE,77276
19
+ fsspec/registry.py,sha256=iPIyCIDcSKxgX7ppEFKHEmTubt8Z-YYpN0Eb3K94S3k,11893
20
+ fsspec/spec.py,sha256=7cOUe5PC5Uyf56HtGBUHEoym8ktPj-BI8G4HR8Xd_C8,77298
21
21
  fsspec/transaction.py,sha256=xliRG6U2Zf3khG4xcw9WiB-yAoqJSHEGK_VjHOdtgo0,2398
22
22
  fsspec/utils.py,sha256=A11t25RnpiQ30RO6xeR0Qqlu3fGj8bnc40jg08tlYSI,22980
23
23
  fsspec/implementations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -31,8 +31,9 @@ fsspec/implementations/data.py,sha256=LDLczxRh8h7x39Zjrd-GgzdQHr78yYxDlrv2C9Uxb5
31
31
  fsspec/implementations/dbfs.py,sha256=2Bp-0m9SqlaroDa0KbXxb5BobCyBJ7_5YQBISf3fxbQ,15145
32
32
  fsspec/implementations/dirfs.py,sha256=f1sGnQ9Vf0xTxrXo4jDeBy4Qfq3RTqAEemqBSeb0hwY,12108
33
33
  fsspec/implementations/ftp.py,sha256=sorsczLp_2J3ukONsbZY-11sRZP6H5a3V7XXf6o6ip0,11936
34
+ fsspec/implementations/gist.py,sha256=Ost985hmFr50KsA-QD0shY3hP4KX5qJ9rb5C-X4ehK8,8341
34
35
  fsspec/implementations/git.py,sha256=4SElW9U5d3k3_ITlvUAx59Yk7XLNRTqkGa2C3hCUkWM,3754
35
- fsspec/implementations/github.py,sha256=Lu0TIFAXzEMq60P495NqvaGFlqWh6be8uzKCiQ9sqw4,11702
36
+ fsspec/implementations/github.py,sha256=aCsZL8UvXZgdkcB1RUs3DdLeNrjLKcFsFYeQFDWbBFo,11653
36
37
  fsspec/implementations/http.py,sha256=_gLt0yGbVOYWvE9pK81WCC-3TgbOMOKJYllBU72ALo8,30138
37
38
  fsspec/implementations/http_sync.py,sha256=UydDqSdUBdhiJ1KufzV8rKGrTftFR4QmNV0safILb8g,30133
38
39
  fsspec/implementations/jupyter.py,sha256=B2uj7OEm7yIk-vRSsO37_ND0t0EBvn4B-Su43ibN4Pg,3811
@@ -53,7 +54,7 @@ fsspec/tests/abstract/mv.py,sha256=k8eUEBIrRrGMsBY5OOaDXdGnQUKGwDIfQyduB6YD3Ns,1
53
54
  fsspec/tests/abstract/open.py,sha256=Fi2PBPYLbRqysF8cFm0rwnB41kMdQVYjq8cGyDXp3BU,329
54
55
  fsspec/tests/abstract/pipe.py,sha256=LFzIrLCB5GLXf9rzFKJmE8AdG7LQ_h4bJo70r8FLPqM,402
55
56
  fsspec/tests/abstract/put.py,sha256=7aih17OKB_IZZh1Mkq1eBDIjobhtMQmI8x-Pw-S_aZk,21201
56
- fsspec-2025.5.0.dist-info/METADATA,sha256=XeOIuCPlMldMZeHAMY9e3Zqy_bwyhd8NE7iTjBPpukA,11697
57
- fsspec-2025.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
58
- fsspec-2025.5.0.dist-info/licenses/LICENSE,sha256=LcNUls5TpzB5FcAIqESq1T53K0mzTN0ARFBnaRQH7JQ,1513
59
- fsspec-2025.5.0.dist-info/RECORD,,
57
+ fsspec-2025.5.1.dist-info/METADATA,sha256=pL2uvv5MW4GapXukzVwdZGe3ghW4KRvh75lvDXTkMl4,11697
58
+ fsspec-2025.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
59
+ fsspec-2025.5.1.dist-info/licenses/LICENSE,sha256=LcNUls5TpzB5FcAIqESq1T53K0mzTN0ARFBnaRQH7JQ,1513
60
+ fsspec-2025.5.1.dist-info/RECORD,,