providence 0.0.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.
- providence/__init__.py +8 -0
- providence/checkpoint_utils.py +95 -0
- providence/cli_module/__init__.py +0 -0
- providence/cli_module/cli.py +92 -0
- providence/cli_module/fetch.py +43 -0
- providence/cli_module/io.py +51 -0
- providence/data.py +361 -0
- providence/io.py +519 -0
- providence/package_data.py +54 -0
- providence/typing.py +28 -0
- providence-0.0.1.dist-info/METADATA +112 -0
- providence-0.0.1.dist-info/RECORD +16 -0
- providence-0.0.1.dist-info/WHEEL +5 -0
- providence-0.0.1.dist-info/entry_points.txt +2 -0
- providence-0.0.1.dist-info/licenses/LICENSE +21 -0
- providence-0.0.1.dist-info/top_level.txt +1 -0
providence/__init__.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
""""Utilities for loading and saving checkpoints."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union
|
|
4
|
+
from tempfile import TemporaryDirectory
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
from carabiner import print_err
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from datasets import Dataset, IterableDataset
|
|
12
|
+
else:
|
|
13
|
+
Dataset, IterableDataset = Any, Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _load_json(checkpoint: str, filename: str) -> Dict[str, Any]:
|
|
17
|
+
with open(os.path.join(checkpoint, filename), "r") as f:
|
|
18
|
+
obj = json.load(f)
|
|
19
|
+
return obj
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def save_json(obj, filename: str) -> None:
|
|
23
|
+
with open(filename, "w") as f:
|
|
24
|
+
try:
|
|
25
|
+
json.dump(obj, f, sort_keys=True, indent=4)
|
|
26
|
+
except TypeError as e:
|
|
27
|
+
print_err(f"{obj=}")
|
|
28
|
+
raise e
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _load_hf_dataset(checkpoint, filename) -> Union[Dataset, IterableDataset]:
|
|
33
|
+
from datasets import load_from_disk
|
|
34
|
+
return load_from_disk(os.path.join(checkpoint, filename))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
FILE_LOADING_CALLBACKS = {
|
|
38
|
+
"json": _load_json,
|
|
39
|
+
"hf-dataset": _load_hf_dataset,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_checkpoint_file(
|
|
44
|
+
checkpoint: str,
|
|
45
|
+
filename: str,
|
|
46
|
+
*args,
|
|
47
|
+
callback: Union[str, Callable] = "json",
|
|
48
|
+
none_on_error: bool = False,
|
|
49
|
+
cache_dir: Optional[str] = None,
|
|
50
|
+
**kwargs
|
|
51
|
+
) -> Union[Any, None]:
|
|
52
|
+
from huggingface_hub import snapshot_download
|
|
53
|
+
|
|
54
|
+
obj = None
|
|
55
|
+
if isinstance(callback, str):
|
|
56
|
+
try:
|
|
57
|
+
callback = FILE_LOADING_CALLBACKS[callback.casefold()]
|
|
58
|
+
except KeyError:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
"""
|
|
61
|
+
File loading callback must be callable or name.
|
|
62
|
+
"""
|
|
63
|
+
)
|
|
64
|
+
if os.path.exists(checkpoint):
|
|
65
|
+
obj = callback(checkpoint, filename)
|
|
66
|
+
elif checkpoint.startswith("hf://"):
|
|
67
|
+
checkpoint = checkpoint.split("hf://")[-1]
|
|
68
|
+
if filename.endswith(".hf"):
|
|
69
|
+
filename_pattern = [filename + '/*.arrow', filename + '/*.json']
|
|
70
|
+
else:
|
|
71
|
+
filename_pattern = filename
|
|
72
|
+
with TemporaryDirectory() as tmpdirname:
|
|
73
|
+
try:
|
|
74
|
+
print_err(f"Looking up: {checkpoint} :: {filename}")
|
|
75
|
+
snapshot_download(
|
|
76
|
+
repo_id=checkpoint,
|
|
77
|
+
allow_patterns=filename_pattern,
|
|
78
|
+
local_dir=tmpdirname,
|
|
79
|
+
cache_dir=cache_dir,
|
|
80
|
+
*args, **kwargs
|
|
81
|
+
)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print_err(e)
|
|
84
|
+
if none_on_error:
|
|
85
|
+
return None
|
|
86
|
+
else:
|
|
87
|
+
raise e
|
|
88
|
+
else:
|
|
89
|
+
obj = callback(tmpdirname, filename)
|
|
90
|
+
if obj is not None:
|
|
91
|
+
return obj
|
|
92
|
+
else:
|
|
93
|
+
raise AttributeError(
|
|
94
|
+
f"Could not load anything from {checkpoint=}, {filename=} with {callback=}."
|
|
95
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Command-line interface for providence."""
|
|
2
|
+
|
|
3
|
+
from argparse import FileType
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from carabiner.cliutils import CLIOption, CLICommand, CLIApp
|
|
8
|
+
|
|
9
|
+
from .. import app_name, __version__
|
|
10
|
+
from .fetch import _fetch
|
|
11
|
+
|
|
12
|
+
def main() -> None:
|
|
13
|
+
|
|
14
|
+
input_file = CLIOption(
|
|
15
|
+
'input_file',
|
|
16
|
+
type=FileType('r'),
|
|
17
|
+
default=sys.stdin,
|
|
18
|
+
nargs='?',
|
|
19
|
+
help='Input file. Default: STDIN',
|
|
20
|
+
)
|
|
21
|
+
input_filename = CLIOption(
|
|
22
|
+
'input_file',
|
|
23
|
+
type=str,
|
|
24
|
+
help='Input file.',
|
|
25
|
+
)
|
|
26
|
+
force_write = CLIOption(
|
|
27
|
+
'--force-write-data', '-f',
|
|
28
|
+
action='store_true',
|
|
29
|
+
help='Save data to disk from remote repos, instead of simply saving a reference.',
|
|
30
|
+
)
|
|
31
|
+
cache = CLIOption(
|
|
32
|
+
'--cache',
|
|
33
|
+
type=str,
|
|
34
|
+
default=None,
|
|
35
|
+
help='Where to cache data.',
|
|
36
|
+
)
|
|
37
|
+
output_name = CLIOption(
|
|
38
|
+
'--output', '-o',
|
|
39
|
+
type=str,
|
|
40
|
+
default=False,
|
|
41
|
+
help='Output filename for data table.',
|
|
42
|
+
)
|
|
43
|
+
checkpoint = CLIOption(
|
|
44
|
+
'--checkpoint', '-c',
|
|
45
|
+
type=str,
|
|
46
|
+
default=False,
|
|
47
|
+
help='Output filename for Providence checkpoint.',
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# slice dataset
|
|
51
|
+
slice_start = CLIOption(
|
|
52
|
+
'--start',
|
|
53
|
+
type=int,
|
|
54
|
+
default=None,
|
|
55
|
+
help='First row of dataset to process.',
|
|
56
|
+
)
|
|
57
|
+
slice_end = CLIOption(
|
|
58
|
+
'--end',
|
|
59
|
+
type=int,
|
|
60
|
+
default=None,
|
|
61
|
+
help='Last row of dataset to process. Default: end of dataset.',
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
get_command = CLICommand(
|
|
65
|
+
"get",
|
|
66
|
+
description="Load a local or remote dataset, and save as a reloadable and verifiable providence checkpoint.",
|
|
67
|
+
options=[
|
|
68
|
+
input_filename,
|
|
69
|
+
slice_start,
|
|
70
|
+
slice_end,
|
|
71
|
+
force_write,
|
|
72
|
+
checkpoint,
|
|
73
|
+
output_name,
|
|
74
|
+
cache,
|
|
75
|
+
],
|
|
76
|
+
main=_fetch,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
app = CLIApp(
|
|
80
|
+
app_name,
|
|
81
|
+
description="Data ingestion, validation, and provenance.",
|
|
82
|
+
version=__version__,
|
|
83
|
+
commands=[
|
|
84
|
+
get_command,
|
|
85
|
+
],
|
|
86
|
+
)
|
|
87
|
+
app.run()
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == '__main__':
|
|
92
|
+
main()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from argparse import Namespace
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from carabiner.cliutils import clicommand
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@clicommand("Fetching data with the following parameters")
|
|
8
|
+
def _fetch(args: Namespace) -> None:
|
|
9
|
+
|
|
10
|
+
from .io import _save_dataset
|
|
11
|
+
from ..data import DataResolver
|
|
12
|
+
|
|
13
|
+
resolved = DataResolver(
|
|
14
|
+
args.input_file,
|
|
15
|
+
cache_dir=args.cache,
|
|
16
|
+
)
|
|
17
|
+
if args.start is not None or args.end is not None:
|
|
18
|
+
resolved = resolved.select_rows(
|
|
19
|
+
args.start,
|
|
20
|
+
args.end,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if not args.checkpoint and not args.output:
|
|
24
|
+
raise ValueError("At least one of --checkpoint and --output should be set.")
|
|
25
|
+
|
|
26
|
+
if args.checkpoint:
|
|
27
|
+
output = args.checkpoint
|
|
28
|
+
out_dir = os.path.dirname(output)
|
|
29
|
+
base = os.path.basename(output)
|
|
30
|
+
|
|
31
|
+
resolved.save_checkpoint(output, save_source_data=args.force_write_data)
|
|
32
|
+
|
|
33
|
+
if args.output:
|
|
34
|
+
output = args.output
|
|
35
|
+
out_dir = os.path.dirname(output)
|
|
36
|
+
base = os.path.basename(output)
|
|
37
|
+
if len(out_dir) > 0:
|
|
38
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
39
|
+
_save_dataset(
|
|
40
|
+
resolved.data,
|
|
41
|
+
output,
|
|
42
|
+
)
|
|
43
|
+
return None
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
from carabiner import print_err
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _resolve_and_slice_data(
|
|
6
|
+
data: str,
|
|
7
|
+
start: int | None = None,
|
|
8
|
+
end: int | None = None,
|
|
9
|
+
batch_size: int = 1024,
|
|
10
|
+
cache_dir: str = None
|
|
11
|
+
):
|
|
12
|
+
from ..io import AutoDataset
|
|
13
|
+
|
|
14
|
+
ds = AutoDataset.load(data, cache_dir)
|
|
15
|
+
nrows = ds._dataset.num_rows
|
|
16
|
+
skip = 0 if start is None else start
|
|
17
|
+
take = (nrows if end is None else end) - skip
|
|
18
|
+
if (take - skip) < nrows:
|
|
19
|
+
print_err(f"[INFO] Reading dataset from row {skip} to row {take + skip} / {nrows}.")
|
|
20
|
+
return ds._dataset.skip(skip).take(take)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _save_dataset(
|
|
24
|
+
dataset,
|
|
25
|
+
output: str
|
|
26
|
+
) -> None:
|
|
27
|
+
print_err("[INFO] Saving dataset:\n" + str(dataset) + "\n" + f"at {output} as", end=" ")
|
|
28
|
+
if output.removesuffix(".gz").endswith((".csv", ".tsv", ".txt")):
|
|
29
|
+
print_err("CSV.")
|
|
30
|
+
dataset.to_csv(
|
|
31
|
+
output,
|
|
32
|
+
sep="," if output.removesuffix(".gz").endswith(".csv") else "\t",
|
|
33
|
+
compression='gzip' if output.endswith(".gz") else None,
|
|
34
|
+
)
|
|
35
|
+
elif output.endswith(".json"):
|
|
36
|
+
print_err("JSON.")
|
|
37
|
+
dataset.to_json(output)
|
|
38
|
+
elif output.endswith(".parquet"):
|
|
39
|
+
print_err("Parquet.")
|
|
40
|
+
dataset.to_parquet(output)
|
|
41
|
+
elif output.endswith(".sql"):
|
|
42
|
+
print_err("SQL.")
|
|
43
|
+
dataset.to_sql(output)
|
|
44
|
+
elif output.endswith(".hf"):
|
|
45
|
+
print_err("Hugging Face dataset.")
|
|
46
|
+
dataset.save_to_disk(output)
|
|
47
|
+
else:
|
|
48
|
+
print_err("Hugging Face dataset.")
|
|
49
|
+
dataset.save_to_disk(output + ".hf")
|
|
50
|
+
print_err(f"WARNING: Unsure what format to save as for filename {output}. Defaulted to Hugging Face dataset.")
|
|
51
|
+
return None
|
providence/data.py
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"""Data resolver class."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
from collections.abc import Iterable, Mapping
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from carabiner import print_err
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from datasets import Dataset, DatasetDict, IterableDataset
|
|
12
|
+
from .collate import ColumnCollator
|
|
13
|
+
else:
|
|
14
|
+
Dataset, DatasetDict, IterableDataset = Any, Any, Any
|
|
15
|
+
ColumnCollator = Any
|
|
16
|
+
|
|
17
|
+
from . import app_name, __version__
|
|
18
|
+
from .io import AutoDataset, DataSource, autoload, file_checksum, save_json, load_json
|
|
19
|
+
from .package_data import CACHE_DIR
|
|
20
|
+
from .typing import DataLike
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
DEFAULT_FORMAT: str = "numpy"
|
|
24
|
+
CONFIG_FILENAME: str = "config.json"
|
|
25
|
+
DATA_FILENAME: str = "data.parquet"
|
|
26
|
+
EXAMPLE_FILENAME: str = "example.parquet"
|
|
27
|
+
SCHEMA_VERSION: str = "1"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DataResolver:
|
|
31
|
+
"""Resolve datasets and track their provenance.
|
|
32
|
+
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
Select rows without modifying the original dataset:
|
|
36
|
+
|
|
37
|
+
>>> data = DataResolver({"x": list(range(10))})
|
|
38
|
+
>>> subset = data.select_rows(2, 5)
|
|
39
|
+
>>> list(subset.data["x"])
|
|
40
|
+
[2, 3, 4]
|
|
41
|
+
>>> len(data.data)
|
|
42
|
+
10
|
|
43
|
+
|
|
44
|
+
Chain selections and restore a reference-only checkpoint:
|
|
45
|
+
|
|
46
|
+
>>> import tempfile
|
|
47
|
+
>>> from pathlib import Path
|
|
48
|
+
>>> import pandas as pd
|
|
49
|
+
>>> with tempfile.TemporaryDirectory() as tmp:
|
|
50
|
+
... source = Path(tmp) / "data.parquet"
|
|
51
|
+
... pd.DataFrame({"x": list(range(10))}).to_parquet(source)
|
|
52
|
+
... selected = DataResolver(source).select_rows(2, 8).select_rows(1, 3)
|
|
53
|
+
... selected.save(Path(tmp) / "checkpoint", save_source_data=False)
|
|
54
|
+
... restored = DataResolver.load(Path(tmp) / "checkpoint")
|
|
55
|
+
... print(list(restored.data["x"]))
|
|
56
|
+
[3, 4]
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
data: str | Mapping[str, Any],
|
|
62
|
+
cache_dir: str | None = None,
|
|
63
|
+
_file_checksum: str | None = None,
|
|
64
|
+
_version: str = __version__,
|
|
65
|
+
_app: str = app_name
|
|
66
|
+
):
|
|
67
|
+
self._version = _version
|
|
68
|
+
self._app = _app
|
|
69
|
+
self._data_requested = data
|
|
70
|
+
self.data = None
|
|
71
|
+
self._file_checksum = _file_checksum
|
|
72
|
+
self.selected_rows = None
|
|
73
|
+
self.data_source = None
|
|
74
|
+
self.data_example = None
|
|
75
|
+
self.cache_dir = cache_dir or CACHE_DIR
|
|
76
|
+
self._data_loaded = False
|
|
77
|
+
self._init()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _init(self) -> None:
|
|
81
|
+
self.data = self._resolve_data(
|
|
82
|
+
self._data_requested,
|
|
83
|
+
)
|
|
84
|
+
if self.data is None:
|
|
85
|
+
raise AttributeError(
|
|
86
|
+
f"Data could not be loaded from {self._data_requested}."
|
|
87
|
+
)
|
|
88
|
+
self._data_loaded = self.data is not None
|
|
89
|
+
|
|
90
|
+
self.data_example, self.data_shape = self._inspect_data(
|
|
91
|
+
self.data,
|
|
92
|
+
self.data_example,
|
|
93
|
+
)
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
def _resolve_data(
|
|
97
|
+
self,
|
|
98
|
+
data: DataLike,
|
|
99
|
+
cache_dir: str | None = None
|
|
100
|
+
) -> Dataset | IterableDataset:
|
|
101
|
+
resolved = AutoDataset.load(
|
|
102
|
+
data,
|
|
103
|
+
cache_dir=cache_dir or self.cache_dir,
|
|
104
|
+
)
|
|
105
|
+
self.data_source = resolved.source
|
|
106
|
+
return resolved._dataset
|
|
107
|
+
|
|
108
|
+
def _apply_selection(
|
|
109
|
+
self,
|
|
110
|
+
start: int | None = None,
|
|
111
|
+
end: int | None = None
|
|
112
|
+
) -> "DataResolver":
|
|
113
|
+
if self.selected_rows is None:
|
|
114
|
+
selected_rows = []
|
|
115
|
+
else:
|
|
116
|
+
selected_rows = self.selected_rows.copy()
|
|
117
|
+
|
|
118
|
+
nrows = self.data.num_rows
|
|
119
|
+
indices = tuple(slice(start, end).indices(nrows))
|
|
120
|
+
skip = indices[0]
|
|
121
|
+
take = indices[1] - skip
|
|
122
|
+
|
|
123
|
+
selected = type(self)(
|
|
124
|
+
self.data.select(range(*indices)),
|
|
125
|
+
cache_dir=self.cache_dir,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
selected.data_source = self.data_source
|
|
129
|
+
selected_rows.append((start, end))
|
|
130
|
+
selected.selected_rows = selected_rows
|
|
131
|
+
return selected
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _normalize_selected_rows(
|
|
135
|
+
start: Iterable[Iterable[int]] | int | None = None,
|
|
136
|
+
end: int | None = None
|
|
137
|
+
):
|
|
138
|
+
if start is None and end is None:
|
|
139
|
+
return None
|
|
140
|
+
if isinstance(start, int) or (start is None and isinstance(end, int)):
|
|
141
|
+
return [(start, end)]
|
|
142
|
+
elif isinstance(start, Iterable) and not isinstance(start, str):
|
|
143
|
+
start = list(start)
|
|
144
|
+
if len(start) == 0:
|
|
145
|
+
raise ValueError(f"start was iterable but length-0: {start}")
|
|
146
|
+
elif len(start) == 2 and all(isinstance(item, int) for item in start):
|
|
147
|
+
start = [start]
|
|
148
|
+
for i, item in enumerate(start):
|
|
149
|
+
if isinstance(item, Iterable) and not isinstance(item, str):
|
|
150
|
+
if len(item) != 2:
|
|
151
|
+
raise ValueError(f"start was iterable but item {i} was not a 2-tuple: {item}")
|
|
152
|
+
else:
|
|
153
|
+
raise ValueError(f"start was iterable but item {i} was not: {item}\n{start=}")
|
|
154
|
+
return [tuple(item) for item in start]
|
|
155
|
+
else:
|
|
156
|
+
raise ValueError(f"Cannot normalize inputs: {start=}, {end=}")
|
|
157
|
+
|
|
158
|
+
def select_rows(
|
|
159
|
+
self,
|
|
160
|
+
start: Iterable[Iterable[int]] | int | None = None,
|
|
161
|
+
end: int | None = None
|
|
162
|
+
) -> "DataResolver":
|
|
163
|
+
"""Return a new resolver containing rows [start:end]."""
|
|
164
|
+
|
|
165
|
+
if start is None and end is None:
|
|
166
|
+
return self # pass through
|
|
167
|
+
selections = self._normalize_selected_rows(start, end)
|
|
168
|
+
data = self
|
|
169
|
+
for item in selections:
|
|
170
|
+
data = data._apply_selection(*item)
|
|
171
|
+
return data
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def _inspect_data(data: Dataset, example=None, format: str = DEFAULT_FORMAT) -> None:
|
|
175
|
+
if example is None:
|
|
176
|
+
if data is not None:
|
|
177
|
+
data = data.with_format(format)
|
|
178
|
+
example = data.take(1)
|
|
179
|
+
else:
|
|
180
|
+
raise AttributeError("Cannot inspect data without loading data or having an example")
|
|
181
|
+
|
|
182
|
+
first_item = example.with_format(format)[:1]
|
|
183
|
+
shape = {
|
|
184
|
+
col: first_item[col].shape[1:]
|
|
185
|
+
if not isinstance(first_item[col], dict)
|
|
186
|
+
else {
|
|
187
|
+
k: v.shape[1:] if v is not None else None
|
|
188
|
+
for k, v in first_item[col].items()
|
|
189
|
+
}
|
|
190
|
+
for col in example.column_names
|
|
191
|
+
}
|
|
192
|
+
return example, shape
|
|
193
|
+
|
|
194
|
+
def save(
|
|
195
|
+
self,
|
|
196
|
+
path: str | os.PathLike,
|
|
197
|
+
*,
|
|
198
|
+
save_source_data: bool | None = None,
|
|
199
|
+
discard_example_data: bool = False
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Save the pipeline and required training-data artefacts.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
==========
|
|
205
|
+
path
|
|
206
|
+
Checkpoint directory.
|
|
207
|
+
save_source_data
|
|
208
|
+
Whether to embed the input dataset.
|
|
209
|
+
|
|
210
|
+
``None`` chooses automatically: immutable remote sources are
|
|
211
|
+
referenced, while local or ephemeral sources are packaged.
|
|
212
|
+
|
|
213
|
+
``True`` always packages the resolved input data.
|
|
214
|
+
|
|
215
|
+
``False`` never packages it.
|
|
216
|
+
discard_example_data
|
|
217
|
+
Normally, save one processed example when available. Set
|
|
218
|
+
discard_example_data=True to turn off.
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
if self.data_source is None:
|
|
223
|
+
source = DataSource()
|
|
224
|
+
else:
|
|
225
|
+
source = self.data_source
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
path = Path(path)
|
|
229
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
230
|
+
|
|
231
|
+
if save_source_data is None:
|
|
232
|
+
save_source_data = not source.is_remote
|
|
233
|
+
if not save_source_data and source.uri is None:
|
|
234
|
+
raise ValueError(
|
|
235
|
+
"Reference-only checkpoint requires a source URI"
|
|
236
|
+
)
|
|
237
|
+
if save_source_data:
|
|
238
|
+
data_filename = str(path / DATA_FILENAME)
|
|
239
|
+
(
|
|
240
|
+
self.data
|
|
241
|
+
.with_format(None)
|
|
242
|
+
.to_parquet(data_filename)
|
|
243
|
+
)
|
|
244
|
+
checksum = file_checksum(data_filename)
|
|
245
|
+
else:
|
|
246
|
+
checksum = None
|
|
247
|
+
|
|
248
|
+
checkpoint_config = {
|
|
249
|
+
"source": source.to_config(),
|
|
250
|
+
"selected_rows": self.selected_rows,
|
|
251
|
+
"artefacts": {
|
|
252
|
+
"source_data": DATA_FILENAME if save_source_data else None,
|
|
253
|
+
"data_checksum": checksum,
|
|
254
|
+
"example": (
|
|
255
|
+
EXAMPLE_FILENAME
|
|
256
|
+
if (
|
|
257
|
+
not discard_example_data
|
|
258
|
+
and self.data_example is not None
|
|
259
|
+
)
|
|
260
|
+
else None
|
|
261
|
+
),
|
|
262
|
+
},
|
|
263
|
+
"schema_version": SCHEMA_VERSION,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
save_json(
|
|
267
|
+
checkpoint_config,
|
|
268
|
+
str(path / CONFIG_FILENAME),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
if (
|
|
272
|
+
not discard_example_data
|
|
273
|
+
and self.data_example is not None
|
|
274
|
+
):
|
|
275
|
+
(
|
|
276
|
+
self.data_example
|
|
277
|
+
.with_format(None)
|
|
278
|
+
.to_parquet(str(path / EXAMPLE_FILENAME))
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
@classmethod
|
|
282
|
+
def load(
|
|
283
|
+
cls,
|
|
284
|
+
path: str | os.PathLike,
|
|
285
|
+
*,
|
|
286
|
+
cache_dir: str | None = None,
|
|
287
|
+
) -> "DataResolver":
|
|
288
|
+
"""Restore a saved DataResolver and its available data artefacts."""
|
|
289
|
+
path = Path(path)
|
|
290
|
+
config = load_json(path / CONFIG_FILENAME)
|
|
291
|
+
|
|
292
|
+
data_source = DataSource.from_config(
|
|
293
|
+
config.get("source")
|
|
294
|
+
)
|
|
295
|
+
artefacts = config.get("artefacts", {})
|
|
296
|
+
data_filename = artefacts.get("source_data")
|
|
297
|
+
data_checksum = artefacts.get("data_checksum")
|
|
298
|
+
example_filename = artefacts.get("example")
|
|
299
|
+
selected_rows = config.get("selected_rows")
|
|
300
|
+
schema_version = config.get("schema_version")
|
|
301
|
+
|
|
302
|
+
if schema_version != SCHEMA_VERSION:
|
|
303
|
+
raise AttributeError(
|
|
304
|
+
f"Checkpoint schema version is {schema_version}, incompatible with package version {SCHEMA_VERSION}."
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if data_filename is not None:
|
|
308
|
+
data_uri = str(path / data_filename)
|
|
309
|
+
this_checksum = file_checksum(data_uri)
|
|
310
|
+
if not this_checksum == data_checksum:
|
|
311
|
+
raise IOError(f"Checksum for {data_uri} should be {data_checksum}, but was {this_checksum}.")
|
|
312
|
+
has_embedded_data = True
|
|
313
|
+
elif data_source is not None:
|
|
314
|
+
if data_source.uri is None:
|
|
315
|
+
raise ValueError(
|
|
316
|
+
f"Reference-only checkpoint has no source URI:\n{data_source}"
|
|
317
|
+
)
|
|
318
|
+
elif (
|
|
319
|
+
not data_source.is_remote
|
|
320
|
+
and not os.path.exists(data_source.uri)
|
|
321
|
+
):
|
|
322
|
+
raise FileNotFoundError(
|
|
323
|
+
"Checkpoint references non-remote data "
|
|
324
|
+
f"at {data_source.uri}, but that source does not exist."
|
|
325
|
+
)
|
|
326
|
+
if not data_source.is_remote:
|
|
327
|
+
data_source.assert_verified()
|
|
328
|
+
data_uri = data_source.uri
|
|
329
|
+
has_embedded_data = False
|
|
330
|
+
else:
|
|
331
|
+
raise AttributeError(f"Checkpoint is missing a data definition!\n{config=}")
|
|
332
|
+
|
|
333
|
+
data = cls(
|
|
334
|
+
data=data_uri,
|
|
335
|
+
cache_dir=cache_dir,
|
|
336
|
+
)
|
|
337
|
+
if not has_embedded_data and selected_rows is not None:
|
|
338
|
+
data = data.select_rows(selected_rows)
|
|
339
|
+
data.selected_rows = cls._normalize_selected_rows(selected_rows)
|
|
340
|
+
if data_source is not None:
|
|
341
|
+
data.data_source = data_source
|
|
342
|
+
|
|
343
|
+
if example_filename is not None:
|
|
344
|
+
data.data_example = autoload(
|
|
345
|
+
path / example_filename,
|
|
346
|
+
cache_dir=cache_dir,
|
|
347
|
+
)
|
|
348
|
+
if data.data_example is not None:
|
|
349
|
+
data.data_example, data.data_shape = data._inspect_data(
|
|
350
|
+
data.data,
|
|
351
|
+
data.data_example,
|
|
352
|
+
)
|
|
353
|
+
return data
|
|
354
|
+
|
|
355
|
+
# for compatibility
|
|
356
|
+
def save_checkpoint(self, *args, **kwargs):
|
|
357
|
+
return self.save(*args, **kwargs)
|
|
358
|
+
|
|
359
|
+
@classmethod
|
|
360
|
+
def load_checkpoint(cls, *args, **kwargs):
|
|
361
|
+
return cls.load(*args, **kwargs)
|