syft-rds 0.1.1.dev4__tar.gz → 0.1.1.dev5__tar.gz
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.
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/PKG-INFO +1 -1
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/pyproject.toml +1 -1
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/__init__.py +1 -1
- syft_rds-0.1.1.dev5/src/syft_rds/client/exceptions.py +18 -0
- syft_rds-0.1.1.dev4/src/syft_rds/client/local_stores/dataset.py → syft_rds-0.1.1.dev5/src/syft_rds/client/local_stores/_dataset.py +1 -1
- syft_rds-0.1.1.dev5/src/syft_rds/client/local_stores/dataset/__init__.py +3 -0
- syft_rds-0.1.1.dev5/src/syft_rds/client/local_stores/dataset/constants.py +4 -0
- syft_rds-0.1.1.dev5/src/syft_rds/client/local_stores/dataset/managers/__init__.py +11 -0
- syft_rds-0.1.1.dev5/src/syft_rds/client/local_stores/dataset/managers/files.py +211 -0
- syft_rds-0.1.1.dev5/src/syft_rds/client/local_stores/dataset/managers/path.py +78 -0
- syft_rds-0.1.1.dev5/src/syft_rds/client/local_stores/dataset/managers/schema.py +89 -0
- syft_rds-0.1.1.dev5/src/syft_rds/client/local_stores/dataset/managers/url.py +48 -0
- syft_rds-0.1.1.dev5/src/syft_rds/client/local_stores/dataset/store.py +223 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/models/base.py +2 -2
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/models/models.py +4 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/store/store.py +6 -3
- syft_rds-0.1.1.dev4/src/syft_rds/client/exceptions.py +0 -6
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/.gitignore +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/LICENSE +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/README.md +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/adjectives.txt +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/css/fonts.css +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/css/model_repr.css +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/css/style.css +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/css/tabulator_pysyft.min.css +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/data_access_template.py.jinja +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/jinja/model_repr.jinja2 +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/jinja/model_repr_file_section.jinja2 +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/jinja/syft_exception.jinja2 +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/jinja/table.jinja2 +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/js/table.js +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/js/tabulator.min.js +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/nouns.txt +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/svg/arrow.svg +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/svg/clipboard.svg +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/svg/copy.svg +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/svg/folder.svg +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/svg/info.svg +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/svg/request.svg +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/svg/search.svg +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/svg/table.svg +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/cli.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/__init__.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/client_registry.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/connection.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/local_store.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/local_stores/__init__.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/local_stores/base.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/local_stores/jobs.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/local_stores/runtime.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/local_stores/user_code.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/rds_client.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/rds_clients/__init__.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/rds_clients/base.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/rds_clients/dataset.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/rds_clients/jobs.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/rds_clients/runtime.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/rds_clients/user_code.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/rds_clients/utils.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/rpc.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/setup.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/client/utils.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/jupyter_utils/__init__.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/jupyter_utils/components.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/jupyter_utils/display.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/jupyter_utils/icons.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/jupyter_utils/table_utils.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/jupyter_utils/tabulator.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/jupyter_utils/types.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/models/__init__.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/models/formatter.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/models/html_format.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/orchestra.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/server/__init__.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/server/app.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/server/router.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/server/routers/job_router.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/server/routers/runtime_router.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/server/routers/user_code_router.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/server/user_file_service.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/store/__init__.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/syft_runtime/README.md +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/syft_runtime/__init__.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/syft_runtime/main.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/utils/name_generator.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/utils/paths.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/utils/render_code_template.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/utils/resources.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/utils/sanitize.py +0 -0
- {syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/utils/zip_utils.py +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class RDSClientError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RDSValidationError(RDSClientError):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DatasetExistsError(RDSClientError):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DatasetNotFoundError(RDSClientError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class JobNotFoundError(RDSClientError):
|
|
18
|
+
pass
|
|
@@ -129,7 +129,7 @@ class DatasetUrlManager:
|
|
|
129
129
|
|
|
130
130
|
@staticmethod
|
|
131
131
|
def get_private_dataset_syftbox_url(
|
|
132
|
-
datasite_email: str, dataset_name: str, path: Union[Path, str]
|
|
132
|
+
datasite_email: str, dataset_name: str, path: Union[Path, str] = None
|
|
133
133
|
) -> SyftBoxURL:
|
|
134
134
|
"""Generate a SyftBox URL for the private dataset."""
|
|
135
135
|
return SyftBoxURL(
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .files import DatasetFilesManager
|
|
2
|
+
from .path import DatasetPathManager
|
|
3
|
+
from .schema import DatasetSchemaManager
|
|
4
|
+
from .url import DatasetUrlManager
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"DatasetFilesManager",
|
|
8
|
+
"DatasetPathManager",
|
|
9
|
+
"DatasetSchemaManager",
|
|
10
|
+
"DatasetUrlManager",
|
|
11
|
+
]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Union
|
|
4
|
+
from loguru import logger
|
|
5
|
+
|
|
6
|
+
from syft_rds.models.models import DatasetCreate
|
|
7
|
+
|
|
8
|
+
from .path import DatasetPathManager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DatasetFilesManager:
|
|
12
|
+
"""Manages file operations for datasets."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, path_manager: DatasetPathManager) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Initialize the files manager.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
path_manager: The path manager to use
|
|
20
|
+
"""
|
|
21
|
+
self._path_manager = path_manager
|
|
22
|
+
|
|
23
|
+
def validate_file_extensions(
|
|
24
|
+
self, path: Union[str, Path], mock_path: Union[str, Path]
|
|
25
|
+
) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Validate that both directories contain the same file extensions.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
path: Path to the original data directory
|
|
31
|
+
mock_path: Path to the mock data directory
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
ValueError: If the directories contain different file extensions
|
|
35
|
+
"""
|
|
36
|
+
path = Path(path)
|
|
37
|
+
mock_path = Path(mock_path)
|
|
38
|
+
|
|
39
|
+
if not (path.is_dir() and mock_path.is_dir()):
|
|
40
|
+
raise ValueError("Both paths must be directories")
|
|
41
|
+
|
|
42
|
+
# Get all file extensions from the first directory into a set
|
|
43
|
+
path_extensions = self._collect_file_extensions(path)
|
|
44
|
+
|
|
45
|
+
# Get all file extensions from the second directory into a set
|
|
46
|
+
mock_extensions = self._collect_file_extensions(mock_path)
|
|
47
|
+
|
|
48
|
+
# Compare the sets of extensions
|
|
49
|
+
if path_extensions != mock_extensions:
|
|
50
|
+
self._report_extension_differences(
|
|
51
|
+
path, mock_path, path_extensions, mock_extensions
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def _collect_file_extensions(self, directory: Path) -> set:
|
|
55
|
+
"""Collect all file extensions from a directory."""
|
|
56
|
+
extensions = set()
|
|
57
|
+
for file_path in directory.glob("**/*"):
|
|
58
|
+
if file_path.is_file() and file_path.suffix:
|
|
59
|
+
extensions.add(file_path.suffix.lower())
|
|
60
|
+
return extensions
|
|
61
|
+
|
|
62
|
+
def _report_extension_differences(
|
|
63
|
+
self, path: Path, mock_path: Path, path_extensions: set, mock_extensions: set
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Report differences in file extensions between directories."""
|
|
66
|
+
extra_in_path = path_extensions - mock_extensions
|
|
67
|
+
extra_in_mock = mock_extensions - path_extensions
|
|
68
|
+
|
|
69
|
+
error_msg = "Directories contain different file extensions:\n"
|
|
70
|
+
if extra_in_path:
|
|
71
|
+
error_msg += f"Extensions in {path} but not in {mock_path}: {', '.join(extra_in_path)}\n"
|
|
72
|
+
if extra_in_mock:
|
|
73
|
+
error_msg += f"Extensions in {mock_path} but not in {path}: {', '.join(extra_in_mock)}"
|
|
74
|
+
|
|
75
|
+
raise ValueError(error_msg)
|
|
76
|
+
|
|
77
|
+
def copy_directory(self, src: Union[str, Path], dest_dir: Path) -> Path:
|
|
78
|
+
"""
|
|
79
|
+
Copy a directory to a destination path.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
src: Source directory
|
|
83
|
+
dest_dir: Destination directory
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Path to the destination directory
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If the source is not a directory
|
|
90
|
+
"""
|
|
91
|
+
src_path = Path(src)
|
|
92
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
|
|
94
|
+
if not src_path.is_dir():
|
|
95
|
+
raise ValueError(f"Source path is not a directory: {src_path}")
|
|
96
|
+
|
|
97
|
+
# Iterate through all items in the source directory
|
|
98
|
+
for item in src_path.iterdir():
|
|
99
|
+
item_dest = dest_dir / item.name
|
|
100
|
+
|
|
101
|
+
if item.is_dir():
|
|
102
|
+
# Recursively copy subdirectories
|
|
103
|
+
shutil.copytree(item, item_dest, dirs_exist_ok=True)
|
|
104
|
+
else:
|
|
105
|
+
# Copy files
|
|
106
|
+
shutil.copy2(item, dest_dir)
|
|
107
|
+
return dest_dir
|
|
108
|
+
|
|
109
|
+
def copy_mock_to_public_syftbox_dir(
|
|
110
|
+
self, dataset_name: str, mock_path: Union[str, Path]
|
|
111
|
+
) -> Path:
|
|
112
|
+
"""Copy mock data to the public SyftBox directory."""
|
|
113
|
+
public_dataset_dir: Path = self._path_manager.get_local_public_dataset_dir(
|
|
114
|
+
dataset_name
|
|
115
|
+
)
|
|
116
|
+
return self.copy_directory(mock_path, public_dataset_dir)
|
|
117
|
+
|
|
118
|
+
def copy_private_to_private_syftbox_dir(
|
|
119
|
+
self,
|
|
120
|
+
dataset_name: str,
|
|
121
|
+
path: Union[str, Path],
|
|
122
|
+
) -> Path:
|
|
123
|
+
"""Copy private data to the private SyftBox directory."""
|
|
124
|
+
private_dataset_dir: Path = self._path_manager.get_local_private_dataset_dir(
|
|
125
|
+
dataset_name
|
|
126
|
+
)
|
|
127
|
+
return self.copy_directory(path, private_dataset_dir)
|
|
128
|
+
|
|
129
|
+
def copy_description_file_to_public_syftbox_dir(
|
|
130
|
+
self, dataset_name: str, description_path: Union[str, Path, None]
|
|
131
|
+
) -> Path | None:
|
|
132
|
+
"""Copy description file to the public SyftBox directory."""
|
|
133
|
+
if not description_path:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
public_dataset_dir: Path = self._path_manager.get_local_public_dataset_dir(
|
|
137
|
+
dataset_name
|
|
138
|
+
)
|
|
139
|
+
if not Path(description_path).exists():
|
|
140
|
+
raise ValueError(f"Description file does not exist: {description_path}")
|
|
141
|
+
dest_path = public_dataset_dir / Path(description_path).name
|
|
142
|
+
shutil.copy2(description_path, dest_path)
|
|
143
|
+
return dest_path
|
|
144
|
+
|
|
145
|
+
def copy_dataset_files(self, dataset_create: DatasetCreate) -> None:
|
|
146
|
+
"""Copy all necessary files for a new dataset."""
|
|
147
|
+
self.copy_mock_to_public_syftbox_dir(
|
|
148
|
+
dataset_create.name, dataset_create.mock_path
|
|
149
|
+
)
|
|
150
|
+
self.copy_description_file_to_public_syftbox_dir(
|
|
151
|
+
dataset_create.name, dataset_create.description_path
|
|
152
|
+
)
|
|
153
|
+
self.copy_private_to_private_syftbox_dir(
|
|
154
|
+
dataset_create.name, dataset_create.path
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def cleanup_dataset_files(self, name: str) -> None:
|
|
158
|
+
"""
|
|
159
|
+
Remove all dataset files for a given dataset name.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
name: Name of the dataset to clean up
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
RuntimeError: If cleanup fails
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
public_dir = self._path_manager.get_local_public_dataset_dir(name)
|
|
169
|
+
private_dir = self._path_manager.get_local_private_dataset_dir(name)
|
|
170
|
+
|
|
171
|
+
self._safe_remove_directory(public_dir)
|
|
172
|
+
self._safe_remove_directory(private_dir)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"Failed to cleanup dataset files: {str(e)}")
|
|
175
|
+
raise RuntimeError(f"Failed to clean up dataset '{name}'") from e
|
|
176
|
+
|
|
177
|
+
def move_dataset_files(
|
|
178
|
+
self, src_dataset_name: str, dst_dataset_name: str, overwrite: bool = False
|
|
179
|
+
) -> None:
|
|
180
|
+
src_public_dir = self._path_manager.get_local_public_dataset_dir(
|
|
181
|
+
src_dataset_name
|
|
182
|
+
)
|
|
183
|
+
src_private_dir: Path = self._path_manager.get_local_private_dataset_dir(
|
|
184
|
+
src_dataset_name
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if not src_public_dir.exists() or not src_private_dir.exists():
|
|
188
|
+
raise FileNotFoundError("Source directories don't exist.")
|
|
189
|
+
|
|
190
|
+
dst_public_dir: Path = self._path_manager.get_local_public_dataset_dir(
|
|
191
|
+
dst_dataset_name
|
|
192
|
+
)
|
|
193
|
+
dst_private_dir: Path = self._path_manager.get_local_private_dataset_dir(
|
|
194
|
+
dst_dataset_name
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if not overwrite and (dst_public_dir.exists() or dst_private_dir.exists()):
|
|
198
|
+
raise FileExistsError("Destination directories already exist.")
|
|
199
|
+
|
|
200
|
+
if dst_public_dir.exists():
|
|
201
|
+
shutil.rmtree(dst_public_dir)
|
|
202
|
+
if dst_private_dir.exists():
|
|
203
|
+
shutil.rmtree(dst_private_dir)
|
|
204
|
+
|
|
205
|
+
shutil.move(src_public_dir, dst_public_dir)
|
|
206
|
+
shutil.move(src_private_dir, dst_private_dir)
|
|
207
|
+
|
|
208
|
+
def _safe_remove_directory(self, directory: Path) -> None:
|
|
209
|
+
"""Safely remove a directory if it exists."""
|
|
210
|
+
if directory.exists():
|
|
211
|
+
shutil.rmtree(directory)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Union
|
|
3
|
+
from syft_core import Client as SyftBoxClient
|
|
4
|
+
|
|
5
|
+
from ..constants import DIRECTORY_DATASETS, DIRECTORY_PRIVATE, DIRECTORY_PUBLIC
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DatasetPathManager:
|
|
9
|
+
"""Manages filesystem paths for dataset operations."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, syftbox_client: SyftBoxClient):
|
|
12
|
+
"""
|
|
13
|
+
Initialize the path manager.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
syftbox_client: The SyftBox client
|
|
17
|
+
host: The host identifier
|
|
18
|
+
"""
|
|
19
|
+
self.syftbox_client = syftbox_client
|
|
20
|
+
|
|
21
|
+
def get_local_public_dataset_dir(self, dataset_name: str) -> Path:
|
|
22
|
+
"""Get the local public directory path for a dataset."""
|
|
23
|
+
return (
|
|
24
|
+
self.syftbox_client.my_datasite
|
|
25
|
+
/ DIRECTORY_PUBLIC
|
|
26
|
+
/ DIRECTORY_DATASETS
|
|
27
|
+
/ dataset_name
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def get_local_private_dataset_dir(self, dataset_name: str) -> Path:
|
|
31
|
+
"""Get the local private directory path for a dataset."""
|
|
32
|
+
return (
|
|
33
|
+
self.syftbox_client.my_datasite
|
|
34
|
+
/ DIRECTORY_PRIVATE
|
|
35
|
+
/ DIRECTORY_DATASETS
|
|
36
|
+
/ dataset_name
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def get_local_public_datasets_dir(self) -> Path:
|
|
40
|
+
"""Get the local base public directory for all datasets."""
|
|
41
|
+
return self.syftbox_client.my_datasite / DIRECTORY_PUBLIC / DIRECTORY_DATASETS
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def syftbox_client_email(self) -> str:
|
|
45
|
+
"""Get the email of the SyftBox client."""
|
|
46
|
+
return self.syftbox_client.email
|
|
47
|
+
|
|
48
|
+
def validate_path_exists(self, path: Union[str, Path]) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Validate that a path exists.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
path: The path to validate
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: If the path doesn't exist
|
|
57
|
+
"""
|
|
58
|
+
if not Path(path).exists():
|
|
59
|
+
raise FileNotFoundError(f"Path does not exist: {path}")
|
|
60
|
+
|
|
61
|
+
def validate_directory_paths(
|
|
62
|
+
self, path: Union[str, Path], mock_path: Union[str, Path]
|
|
63
|
+
) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Validate that both paths are directories.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
path: The first path to validate
|
|
69
|
+
mock_path: The second path to validate
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If either path is not a directory
|
|
73
|
+
"""
|
|
74
|
+
path, mock_path = Path(path), Path(mock_path)
|
|
75
|
+
if not (path.is_dir() and mock_path.is_dir()):
|
|
76
|
+
raise NotADirectoryError(
|
|
77
|
+
f"Mock and private data paths must be directories: {path} and {mock_path}"
|
|
78
|
+
)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from syft_core import SyftBoxURL
|
|
4
|
+
from syft_rds.models.models import Dataset, DatasetCreate
|
|
5
|
+
from syft_rds.store.store import YAMLStore
|
|
6
|
+
|
|
7
|
+
from .path import DatasetPathManager
|
|
8
|
+
from .url import DatasetUrlManager
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DatasetSchemaManager:
|
|
12
|
+
"""Manages schema operations for datasets."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, path_manager: DatasetPathManager, store: YAMLStore) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Initialize the schema manager.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
path_manager: The path manager to use
|
|
20
|
+
store: The RDS store for persistence
|
|
21
|
+
"""
|
|
22
|
+
self._path_manager = path_manager
|
|
23
|
+
self._schema_store = store
|
|
24
|
+
|
|
25
|
+
def create(self, dataset_create: DatasetCreate) -> Dataset:
|
|
26
|
+
"""
|
|
27
|
+
Create a dataset schema.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
dataset_create: Dataset creation data
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The created dataset
|
|
34
|
+
"""
|
|
35
|
+
syftbox_client_email = self._path_manager.syftbox_client_email
|
|
36
|
+
|
|
37
|
+
# Generate URLs for the dataset components
|
|
38
|
+
mock_url = DatasetUrlManager.get_mock_dataset_syftbox_url(
|
|
39
|
+
syftbox_client_email, dataset_create.name, Path(dataset_create.mock_path)
|
|
40
|
+
)
|
|
41
|
+
private_url = DatasetUrlManager.get_private_dataset_syftbox_url(
|
|
42
|
+
syftbox_client_email, dataset_create.name, Path(dataset_create.path)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
readme_url: SyftBoxURL = (
|
|
46
|
+
DatasetUrlManager.get_readme_syftbox_url(
|
|
47
|
+
syftbox_client_email,
|
|
48
|
+
dataset_create.name,
|
|
49
|
+
Path(dataset_create.description_path),
|
|
50
|
+
)
|
|
51
|
+
if dataset_create.description_path
|
|
52
|
+
else None
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Create the dataset schema object
|
|
56
|
+
# TODO: Could we not unpack DatasetCreate directly?
|
|
57
|
+
dataset = Dataset(
|
|
58
|
+
name=dataset_create.name,
|
|
59
|
+
private=private_url,
|
|
60
|
+
mock=mock_url,
|
|
61
|
+
tags=dataset_create.tags,
|
|
62
|
+
summary=dataset_create.summary,
|
|
63
|
+
readme=readme_url,
|
|
64
|
+
auto_approval=dataset_create.auto_approval,
|
|
65
|
+
)
|
|
66
|
+
if dataset_create.runtime:
|
|
67
|
+
dataset.runtime = dataset_create.runtime
|
|
68
|
+
|
|
69
|
+
# Persist the schema to store
|
|
70
|
+
self._schema_store.create(dataset)
|
|
71
|
+
return dataset
|
|
72
|
+
|
|
73
|
+
def delete(self, name: str) -> bool:
|
|
74
|
+
"""
|
|
75
|
+
Delete a dataset by name.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
name: Name of the dataset to delete
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
True if deleted, False if not found
|
|
82
|
+
"""
|
|
83
|
+
queried_result: list[Dataset] = self._schema_store.get_all(
|
|
84
|
+
filters={"name": name}
|
|
85
|
+
)
|
|
86
|
+
if not queried_result:
|
|
87
|
+
return False
|
|
88
|
+
first_res: Dataset = queried_result[0]
|
|
89
|
+
return self._schema_store.delete(first_res.uid)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Union
|
|
3
|
+
from syft_core import SyftBoxURL
|
|
4
|
+
|
|
5
|
+
from ..constants import DIRECTORY_DATASETS, DIRECTORY_PRIVATE, DIRECTORY_PUBLIC
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DatasetUrlManager:
|
|
9
|
+
"""Manages SyftBox URLs for datasets."""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def get_mock_dataset_syftbox_url(
|
|
13
|
+
datasite_email: str, dataset_name: str, mock_path: Union[Path, str] = None
|
|
14
|
+
) -> SyftBoxURL:
|
|
15
|
+
"""Generate a SyftBox URL for the mock dataset."""
|
|
16
|
+
return SyftBoxURL(
|
|
17
|
+
f"syft://{datasite_email}/{DIRECTORY_PUBLIC}/{DIRECTORY_DATASETS}/{dataset_name}"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def get_private_dataset_syftbox_url(
|
|
22
|
+
datasite_email: str, dataset_name: str, path: Union[Path, str] = None
|
|
23
|
+
) -> SyftBoxURL:
|
|
24
|
+
"""Generate a SyftBox URL for the private dataset."""
|
|
25
|
+
return SyftBoxURL(
|
|
26
|
+
f"syft://{datasite_email}/{DIRECTORY_PRIVATE}/{DIRECTORY_DATASETS}/{dataset_name}"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def get_readme_syftbox_url(
|
|
31
|
+
datasite_email: str, dataset_name: str, readme_path: Union[Path, str]
|
|
32
|
+
) -> SyftBoxURL:
|
|
33
|
+
"""Generate a SyftBox URL for the readme file."""
|
|
34
|
+
return SyftBoxURL(
|
|
35
|
+
f"syft://{datasite_email}/{DIRECTORY_PUBLIC}/{DIRECTORY_DATASETS}/{dataset_name}/{Path(readme_path).name}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def update_readme_syftbox_url(
|
|
40
|
+
url: SyftBoxURL, *, dataset_name: str = None
|
|
41
|
+
) -> SyftBoxURL:
|
|
42
|
+
path = Path(url.path)
|
|
43
|
+
datasite_email = url.host
|
|
44
|
+
|
|
45
|
+
if dataset_name:
|
|
46
|
+
path = path.parent.parent / dataset_name / path.name
|
|
47
|
+
|
|
48
|
+
return SyftBoxURL(f"syft://{datasite_email}/{path}")
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import traceback
|
|
3
|
+
from typing import TYPE_CHECKING, Final, Type, Union
|
|
4
|
+
from syft_rds.client.exceptions import DatasetExistsError, DatasetNotFoundError
|
|
5
|
+
from syft_rds.client.local_stores.base import CRUDLocalStore
|
|
6
|
+
from syft_rds.models.models import (
|
|
7
|
+
Dataset,
|
|
8
|
+
DatasetCreate,
|
|
9
|
+
DatasetUpdate,
|
|
10
|
+
GetAllRequest,
|
|
11
|
+
GetOneRequest,
|
|
12
|
+
)
|
|
13
|
+
from syft_core import Client as SyftBoxClient
|
|
14
|
+
|
|
15
|
+
from .managers import (
|
|
16
|
+
DatasetPathManager,
|
|
17
|
+
DatasetFilesManager,
|
|
18
|
+
DatasetUrlManager,
|
|
19
|
+
DatasetSchemaManager,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from syft_rds.client.rds_client import RDSClientConfig
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DatasetLocalStore(CRUDLocalStore[Dataset, DatasetCreate, DatasetUpdate]):
|
|
27
|
+
"""Local store for dataset operations."""
|
|
28
|
+
|
|
29
|
+
ITEM_TYPE: Final[Type[Dataset]] = Dataset
|
|
30
|
+
|
|
31
|
+
def __init__(self, config: "RDSClientConfig", syftbox_client: SyftBoxClient):
|
|
32
|
+
"""
|
|
33
|
+
Initialize the dataset local store.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
config: The RDS client configuration
|
|
37
|
+
syftbox_client: The SyftBox client
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(config, syftbox_client)
|
|
40
|
+
self._path_manager = DatasetPathManager(self.syftbox_client)
|
|
41
|
+
self._files_manager = DatasetFilesManager(self._path_manager)
|
|
42
|
+
self._schema_manager = DatasetSchemaManager(self._path_manager, self.store)
|
|
43
|
+
|
|
44
|
+
def _validate_dataset_paths(
|
|
45
|
+
self,
|
|
46
|
+
name: str,
|
|
47
|
+
path: Union[str, Path],
|
|
48
|
+
mock_path: Union[str, Path],
|
|
49
|
+
) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Validate all aspects of dataset paths.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
name: Dataset name
|
|
55
|
+
path: Path to private data
|
|
56
|
+
mock_path: Path to mock data
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
DatasetExistsError: if the Dataset name already exists
|
|
60
|
+
FileNotFoundError: if the private/mock paths don't exist
|
|
61
|
+
NotADirectoryError: if the provided private/mock paths are not directories
|
|
62
|
+
"""
|
|
63
|
+
self._validate_dataset_name_unique(name)
|
|
64
|
+
self._validate_path_existence(path, mock_path)
|
|
65
|
+
self._validate_file_extensions(path, mock_path)
|
|
66
|
+
|
|
67
|
+
def _validate_dataset_name_unique(self, name: str) -> None:
|
|
68
|
+
"""Validate that dataset name is unique."""
|
|
69
|
+
if (
|
|
70
|
+
self._path_manager.get_local_public_dataset_dir(name).exists()
|
|
71
|
+
or self._path_manager.get_local_private_dataset_dir(name).exists()
|
|
72
|
+
):
|
|
73
|
+
raise DatasetExistsError(f"Dataset with name '{name}' already exists")
|
|
74
|
+
|
|
75
|
+
def _validate_path_existence(
|
|
76
|
+
self, path: Union[str, Path], mock_path: Union[str, Path]
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Validate that paths exist and are directories."""
|
|
79
|
+
self._path_manager.validate_path_exists(path)
|
|
80
|
+
self._path_manager.validate_path_exists(mock_path)
|
|
81
|
+
self._path_manager.validate_directory_paths(path, mock_path)
|
|
82
|
+
|
|
83
|
+
def _validate_file_extensions(
|
|
84
|
+
self, path: Union[str, Path], mock_path: Union[str, Path]
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Validate that file extensions match between directories."""
|
|
87
|
+
self._files_manager.validate_file_extensions(path, mock_path)
|
|
88
|
+
|
|
89
|
+
def create(self, dataset_create: DatasetCreate) -> Dataset:
|
|
90
|
+
"""
|
|
91
|
+
Create a new dataset.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
dataset_create: Dataset creation data
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The created dataset
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
RuntimeError: If creation fails
|
|
101
|
+
"""
|
|
102
|
+
self._validate_dataset_paths(
|
|
103
|
+
dataset_create.name,
|
|
104
|
+
dataset_create.path,
|
|
105
|
+
dataset_create.mock_path,
|
|
106
|
+
)
|
|
107
|
+
try:
|
|
108
|
+
self._files_manager.copy_dataset_files(dataset_create)
|
|
109
|
+
dataset = self._schema_manager.create(dataset_create)
|
|
110
|
+
return dataset._register_client_id_recursive(self.config.uid)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
self._files_manager.cleanup_dataset_files(dataset_create.name)
|
|
113
|
+
self._schema_manager.delete(dataset_create.name)
|
|
114
|
+
raise RuntimeError(
|
|
115
|
+
f"Failed to create dataset '{dataset_create.name}': {str(e)}"
|
|
116
|
+
) from e
|
|
117
|
+
|
|
118
|
+
def get_all(self, request: GetAllRequest) -> list[Dataset]:
|
|
119
|
+
"""
|
|
120
|
+
Get all datasets.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
request: The get all request object
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of all datasets
|
|
127
|
+
"""
|
|
128
|
+
return super().get_all(request)
|
|
129
|
+
|
|
130
|
+
def update(self, update_item: DatasetUpdate) -> Dataset:
|
|
131
|
+
"""
|
|
132
|
+
Update an existing dataset.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
update_dataset: The dataset update data
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The updated dataset
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
RuntimeError: If update fails
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
existing_dataset = self.store.get_by_uid(update_item.uid)
|
|
145
|
+
if existing_dataset is None:
|
|
146
|
+
raise DatasetNotFoundError(
|
|
147
|
+
f"Dataset with uid {update_item.uid} not found"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
updated_dataset = existing_dataset.apply_update(update_item, in_place=False)
|
|
151
|
+
|
|
152
|
+
if update_item.name:
|
|
153
|
+
self._validate_dataset_name_unique(update_item.name)
|
|
154
|
+
syftbox_client_email = self._path_manager.syftbox_client_email
|
|
155
|
+
old_dataset_name = existing_dataset.name
|
|
156
|
+
new_dataset_name = update_item.name
|
|
157
|
+
|
|
158
|
+
self._files_manager.move_dataset_files(
|
|
159
|
+
old_dataset_name, new_dataset_name
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
new_private_url = DatasetUrlManager.get_private_dataset_syftbox_url(
|
|
163
|
+
syftbox_client_email, updated_dataset.name
|
|
164
|
+
)
|
|
165
|
+
new_mock_url = DatasetUrlManager.get_mock_dataset_syftbox_url(
|
|
166
|
+
syftbox_client_email, updated_dataset.name
|
|
167
|
+
)
|
|
168
|
+
new_readme_url = DatasetUrlManager.update_readme_syftbox_url(
|
|
169
|
+
updated_dataset.readme, dataset_name=updated_dataset.name
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
updated_dataset.private = new_private_url
|
|
173
|
+
updated_dataset.mock = new_mock_url
|
|
174
|
+
updated_dataset.readme = new_readme_url
|
|
175
|
+
|
|
176
|
+
if update_item.path:
|
|
177
|
+
self._files_manager.copy_private_to_private_syftbox_dir(
|
|
178
|
+
updated_dataset.name, update_item.path
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
updated_dataset = self.store.update(updated_dataset.uid, updated_dataset)
|
|
182
|
+
return updated_dataset._register_client_id_recursive(self.config.uid)
|
|
183
|
+
except DatasetExistsError:
|
|
184
|
+
raise
|
|
185
|
+
except Exception as e:
|
|
186
|
+
print(traceback.format_exc())
|
|
187
|
+
raise RuntimeError(
|
|
188
|
+
f"Failed to update dataset with uid {update_item.uid}: {str(e)}"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def get(self, request: GetOneRequest) -> Dataset:
|
|
192
|
+
"""
|
|
193
|
+
Get a dataset based on name / id
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
request: The get one request object
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
The dataset
|
|
200
|
+
"""
|
|
201
|
+
return super().get_one(request)
|
|
202
|
+
|
|
203
|
+
def delete_by_name(self, name: str) -> bool:
|
|
204
|
+
"""
|
|
205
|
+
Delete a dataset by name.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
name: Name of the dataset to delete
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
True if deleted, False if not found
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
RuntimeError: If deletion fails
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
schema_deleted = self._schema_manager.delete(name)
|
|
218
|
+
if schema_deleted:
|
|
219
|
+
self._files_manager.cleanup_dataset_files(name)
|
|
220
|
+
return True
|
|
221
|
+
return False
|
|
222
|
+
except Exception as e:
|
|
223
|
+
raise RuntimeError(f"Failed to delete dataset '{name}'") from e
|
|
@@ -83,14 +83,14 @@ class ItemBase(BaseModel, ABC):
|
|
|
83
83
|
f"Cannot apply update with UID {other.uid} to instance with UID {self.uid}"
|
|
84
84
|
)
|
|
85
85
|
if isinstance(other, type(self)):
|
|
86
|
-
update_dict = other.model_dump()
|
|
86
|
+
update_dict = other.model_dump(exclude_unset=True, exclude_none=True)
|
|
87
87
|
elif isinstance(other, ItemBaseUpdate):
|
|
88
88
|
update_target_type = other.get_target_model()
|
|
89
89
|
if other.get_target_model() is not type(self):
|
|
90
90
|
raise ValueError(
|
|
91
91
|
f"Attempted to apply update for {update_target_type} to {type(self)}"
|
|
92
92
|
)
|
|
93
|
-
update_dict = other.model_dump(exclude_unset=True)
|
|
93
|
+
update_dict = other.model_dump(exclude_unset=True, exclude_none=True)
|
|
94
94
|
update_dict["updated_at"] = _utcnow()
|
|
95
95
|
else:
|
|
96
96
|
raise TypeError(
|
|
@@ -436,6 +436,10 @@ class DatasetCreate(ItemBaseCreate[Dataset]):
|
|
|
436
436
|
|
|
437
437
|
|
|
438
438
|
class DatasetUpdate(ItemBaseUpdate[Dataset]):
|
|
439
|
+
name: Optional[str] = None
|
|
440
|
+
path: Optional[str] = Field(
|
|
441
|
+
description="Path of the new private dataset directory", default=None
|
|
442
|
+
)
|
|
439
443
|
summary: Optional[str] = None
|
|
440
444
|
auto_approval: Optional[list[str]] = Field(
|
|
441
445
|
default_factory=list,
|
|
@@ -195,7 +195,7 @@ class YAMLStore(Generic[T]):
|
|
|
195
195
|
return record
|
|
196
196
|
|
|
197
197
|
@ensure_store_exists
|
|
198
|
-
def update(self, uid: str | UUID, record: T) ->
|
|
198
|
+
def update(self, uid: str | UUID, record: T) -> T:
|
|
199
199
|
"""
|
|
200
200
|
Update a record by UID
|
|
201
201
|
|
|
@@ -204,14 +204,17 @@ class YAMLStore(Generic[T]):
|
|
|
204
204
|
record: New data to update with
|
|
205
205
|
|
|
206
206
|
Returns:
|
|
207
|
-
Updated record
|
|
207
|
+
Updated record
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
FileNotFoundError: If no record with the given UID exists
|
|
208
211
|
"""
|
|
209
212
|
if not isinstance(record, self.item_type):
|
|
210
213
|
raise TypeError(f"`record` must be of type {self.item_type.__name__}")
|
|
211
214
|
|
|
212
215
|
existing_record = self.get_by_uid(uid)
|
|
213
216
|
if not existing_record:
|
|
214
|
-
|
|
217
|
+
raise FileNotFoundError(f"Record with UID {uid} does not exist")
|
|
215
218
|
|
|
216
219
|
# Update the record
|
|
217
220
|
updated_record = existing_record.model_copy(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/css/tabulator_pysyft.min.css
RENAMED
|
File without changes
|
{syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/data_access_template.py.jinja
RENAMED
|
File without changes
|
|
File without changes
|
{syft_rds-0.1.1.dev4 → syft_rds-0.1.1.dev5}/src/syft_rds/assets/jinja/model_repr_file_section.jinja2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|