fsspec-data 0.2.3__cp310-abi3-win_amd64.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.
@@ -0,0 +1,36 @@
1
+ from .filesystem import DataFileSystem
2
+ from .interchange import (
3
+ DEFAULT_REGISTRY,
4
+ Codec,
5
+ CodecCapabilities,
6
+ CodecRegistry,
7
+ DataFormat,
8
+ DecodedBatches,
9
+ DecodedBatchStream,
10
+ FieldMapping,
11
+ InterchangePlan,
12
+ InterchangeRequest,
13
+ PlannedBatchStream,
14
+ SchemaPolicy,
15
+ plan_schema,
16
+ )
17
+
18
+ __version__ = "0.2.3"
19
+
20
+ __all__ = [
21
+ "Codec",
22
+ "CodecCapabilities",
23
+ "CodecRegistry",
24
+ "DEFAULT_REGISTRY",
25
+ "DataFormat",
26
+ "DataFileSystem",
27
+ "DecodedBatchStream",
28
+ "DecodedBatches",
29
+ "FieldMapping",
30
+ "InterchangePlan",
31
+ "InterchangeRequest",
32
+ "PlannedBatchStream",
33
+ "SchemaPolicy",
34
+ "__version__",
35
+ "plan_schema",
36
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,2 @@
1
+ from .http import HTTPFilesystem
2
+ from .sqlalchemy import SQLAlchemyFilesystem
@@ -0,0 +1,222 @@
1
+ from fsspec import AbstractFileSystem
2
+ from fsspec.spec import AbstractBufferedFile
3
+
4
+
5
+ class BaseFileSystem(AbstractFileSystem):
6
+ """
7
+ Docs: https://filesystem-spec.readthedocs.io/en/latest/developer.html#implementing-a-backend
8
+ Minimal: https://github.com/fsspec/filesystem_spec/blob/45dcfa99e2dc320bf072c28bef767b71f8299a4a/fsspec/implementations/github.py#L10
9
+ """
10
+
11
+ # def start_transaction(self):
12
+ # def end_transaction(self):
13
+ # def invalidate_cache(self, path=None):
14
+
15
+ def mkdir(self, path, create_parents=True, **kwargs):
16
+ """Create directory entry at path
17
+
18
+ For systems that don't have true directories, may create an for
19
+ this instance only and not touch the real filesystem
20
+
21
+ Parameters
22
+ ----------
23
+ path: str
24
+ location
25
+ create_parents: bool
26
+ if True, this is equivalent to ``makedirs``
27
+ kwargs:
28
+ may be permissions, etc.
29
+ """
30
+ ...
31
+
32
+ def makedirs(self, path, exist_ok=False):
33
+ """Recursively make directories
34
+
35
+ Creates directory at path and any intervening required directories.
36
+ Raises exception if, for instance, the path already exists but is a
37
+ file.
38
+
39
+ Parameters
40
+ ----------
41
+ path: str
42
+ leaf directory name
43
+ exist_ok: bool (False)
44
+ If False, will error if the target already exists
45
+ """
46
+ ...
47
+
48
+ def rmdir(self, path):
49
+ """Remove a directory, if empty"""
50
+ ...
51
+
52
+ def ls(self, path, detail=True, **kwargs):
53
+ """List objects at path.
54
+
55
+ This should include subdirectories and files at that location. The
56
+ difference between a file and a directory must be clear when details
57
+ are requested.
58
+
59
+ The specific keys, or perhaps a FileInfo class, or similar, is TBD,
60
+ but must be consistent across implementations.
61
+ Must include:
62
+
63
+ - full path to the entry (without protocol)
64
+ - size of the entry, in bytes. If the value cannot be determined, will
65
+ be ``None``.
66
+ - type of entry, "file", "directory" or other
67
+
68
+ Additional information
69
+ may be present, aproriate to the file-system, e.g., generation,
70
+ checksum, etc.
71
+
72
+ May use refresh=True|False to allow use of self._ls_from_cache to
73
+ check for a saved listing and avoid calling the backend. This would be
74
+ common where listing may be expensive.
75
+
76
+ Parameters
77
+ ----------
78
+ path: str
79
+ detail: bool
80
+ if True, gives a list of dictionaries, where each is the same as
81
+ the result of ``info(path)``. If False, gives a list of paths
82
+ (str).
83
+ kwargs: may have additional backend-specific options, such as version
84
+ information
85
+
86
+ Returns
87
+ -------
88
+ List of strings if detail is False, or list of directory information
89
+ dicts if detail is True.
90
+ """
91
+ raise NotImplementedError
92
+
93
+ def cp_file(self, path1, path2, **kwargs):
94
+ raise NotImplementedError
95
+
96
+ def _rm(self, path):
97
+ """Delete one file"""
98
+ # this is the old name for the method, prefer rm_file
99
+ raise NotImplementedError
100
+
101
+ def created(self, path):
102
+ """Return the created timestamp of a file as a datetime.datetime"""
103
+ raise NotImplementedError
104
+
105
+ def modified(self, path):
106
+ """Return the modified timestamp of a file as a datetime.datetime"""
107
+ raise NotImplementedError
108
+
109
+ # def walk(self, path, maxdepth=None, **kwargs):
110
+ # def find(self, path, maxdepth=None, withdirs=False, **kwargs):
111
+ # def du(self, path, total=True, maxdepth=None, **kwargs):
112
+ # def glob(self, path, **kwargs):
113
+ # def exists(self, path, **kwargs):
114
+ # def lexists(self, path, **kwargs):
115
+ # def info(self, path, **kwargs):
116
+ # def checksum(self, path):
117
+ # def size(self, path):
118
+ # def sizes(self, paths):
119
+ # def isdir(self, path):
120
+ # def isfile(self, path):
121
+ # def cat_file(self, path, start=None, end=None, **kwargs):
122
+ # def pipe_file(self, path, value, **kwargs):
123
+ # def pipe(self, path, value=None, **kwargs):
124
+ # def cat_ranges(self, paths, starts, ends, max_gap=None, **kwargs):
125
+ # def cat(self, path, recursive=False, on_error="raise", **kwargs):
126
+ # def get_file(self, rpath, lpath, callback=_DEFAULT_CALLBACK, **kwargs):
127
+ # def get(self, rpath, lpath, recursive=False, callback=_DEFAULT_CALLBACK, **kwargs):
128
+ # def put_file(self, lpath, rpath, callback=_DEFAULT_CALLBACK, **kwargs):
129
+ # def put(self, lpath, rpath, recursive=False, callback=_DEFAULT_CALLBACK, **kwargs):
130
+ # def head(self, path, size=1024):
131
+ # def tail(self, path, size=1024):
132
+ # def copy(self, path1, path2, recursive=False, on_error=None, **kwargs):
133
+ # def expand_path(self, path, recursive=False, maxdepth=None):
134
+ # def mv(self, path1, path2, recursive=False, maxdepth=None, **kwargs):
135
+ # def rm_file(self, path):
136
+ # def rm(self, path, recursive=False, maxdepth=None):
137
+ # def _parent(cls, path):
138
+ # def _open(
139
+ # def open(
140
+ # def touch(self, path, truncate=True, **kwargs):
141
+ # def ukey(self, path):
142
+ # def read_block(self, fn, offset, length, delimiter=None):
143
+ # def to_json(self):
144
+ # def from_json(blob):
145
+ # def get_mapper(self, root="", check=False, create=False, missing_exceptions=None):
146
+ # ------------------------------------------------------------------------
147
+ # Aliases
148
+ # def makedir(self, path, create_parents=True, **kwargs):
149
+ # def mkdirs(self, path, exist_ok=False):
150
+ # def listdir(self, path, detail=True, **kwargs):
151
+ # def cp(self, path1, path2, **kwargs):
152
+ # def move(self, path1, path2, **kwargs):
153
+ # def stat(self, path, **kwargs):
154
+ # def disk_usage(self, path, total=True, maxdepth=None, **kwargs):
155
+ # def rename(self, path1, path2, **kwargs):
156
+ # def delete(self, path, recursive=False, maxdepth=None):
157
+ # def upload(self, lpath, rpath, recursive=False, **kwargs):
158
+ # def download(self, rpath, lpath, recursive=False, **kwargs):
159
+
160
+ def sign(self, path, expiration=100, **kwargs):
161
+ """Create a signed URL representing the given path
162
+
163
+ Some implementations allow temporary URLs to be generated, as a
164
+ way of delegating credentials.
165
+
166
+ Parameters
167
+ ----------
168
+ path : str
169
+ The path on the filesystem
170
+ expiration : int
171
+ Number of seconds to enable the URL for (if supported)
172
+
173
+ Returns
174
+ -------
175
+ URL : str
176
+ The signed URL
177
+
178
+ Raises
179
+ ------
180
+ NotImplementedError : if method is not implemented for a filesystem
181
+ """
182
+ raise NotImplementedError("Sign is not implemented for this filesystem")
183
+
184
+
185
+ class BaseBufferedFile(AbstractBufferedFile):
186
+ # def details(self):
187
+ # def details(self, value):
188
+ # def full_name(self):
189
+ # def closed(self):
190
+ # def closed(self, c):
191
+
192
+ def commit(self):
193
+ """Move from temp to final destination"""
194
+
195
+ def discard(self):
196
+ """Throw away temporary file"""
197
+
198
+ # def info(self):
199
+ # def tell(self):
200
+ # def seek(self, loc, whence=0):
201
+ # def write(self, data):
202
+ # def flush(self, force=False):
203
+ # def _upload_chunk(self, final=False):
204
+
205
+ def _initiate_upload(self):
206
+ """Create remote file/upload"""
207
+ pass
208
+
209
+ def _fetch_range(self, start, end):
210
+ """Get the specified set of bytes from remote"""
211
+ raise NotImplementedError
212
+
213
+ # def read(self, length=-1):
214
+ # def readinto(self, b):
215
+ # def readuntil(self, char=b"\n", blocks=None):
216
+ # def readline(self):
217
+ # def readlines(self):
218
+ # def readinto1(self, b):
219
+ # def close(self):
220
+ # def readable(self):
221
+ # def seekable(self):
222
+ # def writable(self):
@@ -0,0 +1,4 @@
1
+ from .base import BaseFileSystem
2
+
3
+
4
+ class HTTPFilesystem(BaseFileSystem): ...
@@ -0,0 +1,4 @@
1
+ from .base import BaseFileSystem
2
+
3
+
4
+ class SQLAlchemyFilesystem(BaseFileSystem): ...
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ import tempfile
4
+ from collections.abc import Mapping, Sequence
5
+ from typing import Any
6
+
7
+ import pyarrow as pa
8
+ from fsspec import AbstractFileSystem, filesystem
9
+ from fsspec.core import url_to_fs
10
+ from fsspec.implementations.chained import ChainedFileSystem
11
+
12
+ from .interchange import DEFAULT_REGISTRY, DataFormat, InterchangeRequest, SchemaPolicy
13
+
14
+
15
+ class DataFileSystem(ChainedFileSystem):
16
+ """Read-only format and schema conversion layered over another filesystem."""
17
+
18
+ protocol = "fsspec-data"
19
+
20
+ def __init__(
21
+ self,
22
+ fo: str,
23
+ target_protocol: str | None = None,
24
+ target_options: dict[str, Any] | None = None,
25
+ fs: AbstractFileSystem | None = None,
26
+ provided_format: DataFormat | str | None = None,
27
+ requested_format: DataFormat | str | None = None,
28
+ provided_schema: pa.Schema | Mapping[str, Any] | Sequence[Mapping[str, Any]] | None = None,
29
+ requested_schema: pa.Schema | Mapping[str, Any] | Sequence[Mapping[str, Any]] | None = None,
30
+ schema_policy: SchemaPolicy | str = SchemaPolicy.EXACT,
31
+ batch_size: int = 1024,
32
+ row_limit: int | None = None,
33
+ byte_limit: int | None = None,
34
+ spool_max_size: int = 8 * 1024 * 1024,
35
+ **kwargs: Any,
36
+ ) -> None:
37
+ super().__init__(**kwargs)
38
+ if fs is not None and target_protocol is not None:
39
+ raise ValueError("provide either fs or target_protocol, not both")
40
+ if fs is None:
41
+ if target_protocol is None:
42
+ fs, fo = url_to_fs(fo, **(target_options or {}))
43
+ else:
44
+ fs = filesystem(target_protocol, **(target_options or {}))
45
+
46
+ self.fs = fs
47
+ self.fo = fs._strip_protocol(fo)
48
+ self.provided_format = DataFormat(provided_format) if provided_format is not None else None
49
+ self.requested_format = DataFormat(requested_format) if requested_format is not None else None
50
+ self.provided_schema = _schema_from_options(provided_schema)
51
+ self.requested_schema = _schema_from_options(requested_schema)
52
+ self.schema_policy = SchemaPolicy(schema_policy)
53
+ self.batch_size = batch_size
54
+ self.row_limit = row_limit
55
+ self.byte_limit = byte_limit
56
+ self.spool_max_size = spool_max_size
57
+ self._sizes: dict[str, int] = {}
58
+
59
+ def _open(
60
+ self,
61
+ path: str,
62
+ mode: str = "rb",
63
+ block_size: int | None = None,
64
+ autocommit: bool = True,
65
+ cache_options: dict[str, Any] | None = None,
66
+ **kwargs: Any,
67
+ ):
68
+ del block_size, autocommit, cache_options, kwargs
69
+ if mode != "rb":
70
+ raise ValueError("fsspec-data is a read-only filesystem")
71
+
72
+ path = self._strip_protocol(path)
73
+ file = tempfile.SpooledTemporaryFile(max_size=self.spool_max_size, mode="w+b")
74
+ try:
75
+ self._convert(path, file)
76
+ self._sizes[path] = file.tell()
77
+ file.seek(0)
78
+ except Exception:
79
+ file.close()
80
+ raise
81
+ return file
82
+
83
+ def info(self, path: str, **kwargs: Any) -> dict[str, Any]:
84
+ del kwargs
85
+ path = self._strip_protocol(path)
86
+ size = self._sizes.get(path)
87
+ if size is None:
88
+ with self._open(path) as file:
89
+ file.seek(0, 2)
90
+ size = file.tell()
91
+ return {"name": path, "size": size, "type": "file"}
92
+
93
+ def ls(self, path: str, detail: bool = True, **kwargs: Any):
94
+ path = self._strip_protocol(path)
95
+ if not path:
96
+ return []
97
+ entry = self.info(path, **kwargs)
98
+ return [entry] if detail else [entry["name"]]
99
+
100
+ def _convert(self, path: str, output) -> None:
101
+ provided_format = self.provided_format or _format_from_path(self.fo)
102
+ requested_format = self.requested_format or _format_from_path(path)
103
+ with self.fs.open(self.fo, "rb") as source:
104
+ decoded = DEFAULT_REGISTRY.get(provided_format).iter_batches(
105
+ source,
106
+ schema=self.provided_schema,
107
+ batch_size=self.batch_size,
108
+ row_limit=self.row_limit,
109
+ byte_limit=self.byte_limit,
110
+ )
111
+ if self.provided_schema is not None and not decoded.schema.equals(self.provided_schema, check_metadata=False):
112
+ raise ValueError("provided schema does not match the source schema")
113
+
114
+ provided_schema = self.provided_schema or decoded.schema
115
+ requested_schema = self.requested_schema or provided_schema
116
+ plan = InterchangeRequest(
117
+ provided_format,
118
+ requested_format,
119
+ provided_schema,
120
+ requested_schema,
121
+ self.schema_policy,
122
+ ).plan()
123
+ batches = (plan.apply_batch(batch) for batch in decoded)
124
+ DEFAULT_REGISTRY.get(requested_format).encode_batches_to(batches, output, schema=requested_schema)
125
+
126
+
127
+ _SUFFIX_FORMATS = {
128
+ ".arrow": DataFormat.ARROW,
129
+ ".csv": DataFormat.CSV,
130
+ ".ipc": DataFormat.ARROW,
131
+ ".jsonl": DataFormat.JSONL,
132
+ ".ndjson": DataFormat.JSONL,
133
+ ".parquet": DataFormat.PARQUET,
134
+ ".pq": DataFormat.PARQUET,
135
+ }
136
+
137
+
138
+ def _format_from_path(path: str) -> DataFormat:
139
+ suffix = "." + path.rsplit(".", 1)[-1].lower() if "." in path.rsplit("/", 1)[-1] else ""
140
+ try:
141
+ return _SUFFIX_FORMATS[suffix]
142
+ except KeyError as error:
143
+ raise ValueError(f"cannot infer tabular format from path {path!r}") from error
144
+
145
+
146
+ def _schema_from_options(
147
+ value: pa.Schema | Mapping[str, Any] | Sequence[Mapping[str, Any]] | None,
148
+ ) -> pa.Schema | None:
149
+ if value is None or isinstance(value, pa.Schema):
150
+ return value
151
+ fields = value.get("fields") if isinstance(value, Mapping) else value
152
+ if not isinstance(fields, Sequence) or isinstance(fields, (str, bytes)):
153
+ raise TypeError("schema options must contain a sequence of fields")
154
+ return pa.schema([_field_from_options(field) for field in fields])
155
+
156
+
157
+ def _field_from_options(value: Mapping[str, Any]) -> pa.Field:
158
+ if not isinstance(value, Mapping):
159
+ raise TypeError("schema fields must be mappings")
160
+ try:
161
+ name = value["name"]
162
+ data_type = value["type"]
163
+ except KeyError as error:
164
+ raise ValueError(f"schema field is missing {error.args[0]!r}") from error
165
+ if not isinstance(name, str) or not isinstance(data_type, (str, pa.DataType)):
166
+ raise TypeError("schema field name and type must be strings or PyArrow data types")
167
+ if isinstance(data_type, str):
168
+ data_type = pa.type_for_alias(data_type)
169
+ nullable = value.get("nullable", True)
170
+ if not isinstance(nullable, bool):
171
+ raise TypeError("schema field nullable must be a boolean")
172
+ return pa.field(name, data_type, nullable=nullable)
Binary file