Skip to content

Commit 4fab7f2

Browse files
authored
feat(skore): Add Project.delete function (#1778)
Closes #1734 . > [!CAUTION] > #1777 must be merged before > #1775 must be merged before
1 parent 9fc5a1d commit 4fab7f2

File tree

2 files changed

+178
-75
lines changed

2 files changed

+178
-75
lines changed

skore/src/skore/project/project.py

Lines changed: 90 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,15 @@ class Project(DirNamesMixin):
5050
Otherwise, the project is configured to the ``local`` mode to be persisted on
5151
the user machine in a directory called ``workspace``.
5252
53-
The workspace can be shared between all the projects.
54-
The workspace can be set using kwargs or the envar ``SKORE_WORKSPACE``.
55-
If not, it will be by default set to a ``skore/`` directory in the USER
56-
cache directory:
53+
| The workspace can be shared between all the projects.
54+
| The workspace can be set using kwargs or the environment variable
55+
``SKORE_WORKSPACE``.
56+
| If not, it will be by default set to a ``skore/`` directory in the USER
57+
cache directory:
5758
58-
- in Windows, usually ``C:\Users\%USER%\AppData\Local\skore``,
59-
- in Linux, usually ``${HOME}/.cache/skore``,
60-
- in macOS, usually ``${HOME}/Library/Caches/skore``.
59+
- on Windows, usually ``C:\Users\%USER%\AppData\Local\skore``,
60+
- on Linux, usually ``${HOME}/.cache/skore``,
61+
- on macOS, usually ``${HOME}/Library/Caches/skore``.
6162
6263
Refer to the :ref:`project` section of the user guide for more details.
6364
@@ -76,14 +77,15 @@ class Project(DirNamesMixin):
7677
workspace : Path, mode:local only.
7778
The directory where the local project is persisted.
7879
79-
The workspace can be shared between all the projects.
80-
The workspace can be set using kwargs or the envar ``SKORE_WORKSPACE``.
81-
If not, it will be by default set to a ``skore/`` directory in the USER
82-
cache directory:
80+
| The workspace can be shared between all the projects.
81+
| The workspace can be set using kwargs or the environment variable
82+
``SKORE_WORKSPACE``.
83+
| If not, it will be by default set to a ``skore/`` directory in the USER
84+
cache directory:
8385
84-
- in Windows, usually ``C:\Users\%USER%\AppData\Local\skore``,
85-
- in Linux, usually ``${HOME}/.cache/skore``,
86-
- in macOS, usually ``${HOME}/Library/Caches/skore``.
86+
- on Windows, usually ``C:\Users\%USER%\AppData\Local\skore``,
87+
- on Linux, usually ``${HOME}/.cache/skore``,
88+
- on macOS, usually ``${HOME}/Library/Caches/skore``.
8789
8890
Attributes
8991
----------
@@ -149,32 +151,67 @@ class Project(DirNamesMixin):
149151
"""
150152

151153
__HUB_NAME_PATTERN = re.compile(r"hub://(?P<tenant>[^/]+)/(?P<name>.+)")
154+
152155
reports: _ReportsAccessor
153156
_Project__project: Any
154157
_Project__mode: str
155158
_Project__name: str
156159

157-
def __init__(self, name: str, **kwargs):
160+
@staticmethod
161+
def __setup_plugin(name: str) -> tuple[str, str, Any, dict]:
158162
if not (PLUGINS := entry_points(group="skore.plugins.project")):
159163
raise SystemError("No project plugin found, please install at least one.")
160164

161-
if match := re.match(self.__HUB_NAME_PATTERN, name):
165+
if match := re.match(Project.__HUB_NAME_PATTERN, name):
162166
mode = "hub"
163167
name = match["name"]
164-
kwargs |= {"tenant": match["tenant"], "name": name}
168+
parameters = {"tenant": match["tenant"], "name": name}
165169
else:
166170
mode = "local"
167-
kwargs |= {"name": name}
171+
parameters = {"name": name}
168172

169173
if mode not in PLUGINS.names:
170174
raise ValueError(
171175
f"Unknown mode `{mode}`. "
172176
f"Please install the `skore-{mode}-project` python package."
173177
)
174178

179+
return mode, name, PLUGINS[mode].load(), parameters
180+
181+
def __init__(self, name: str, **kwargs):
182+
r"""
183+
Initialize a project.
184+
185+
Parameters
186+
----------
187+
name : str
188+
The name of the project:
189+
190+
- if the ``name`` takes the form of the URI ``hub://<tenant>/<name>``, the
191+
project is configured to communicate with the ``skore hub``,
192+
- otherwise, the project is configured to communicate with a local storage,
193+
on the user machine.
194+
**kwargs : dict
195+
Extra keyword arguments passed to the project, depending on its mode.
196+
197+
workspace : Path, mode:local only.
198+
The directory where the local project is persisted.
199+
200+
| The workspace can be shared between all the projects.
201+
| The workspace can be set using kwargs or the environment variable
202+
``SKORE_WORKSPACE``.
203+
| If not, it will be by default set to a ``skore/`` directory in the
204+
USER cache directory:
205+
206+
- on Windows, usually ``C:\Users\%USER%\AppData\Local\skore``,
207+
- on Linux, usually ``${HOME}/.cache/skore``,
208+
- on macOS, usually ``${HOME}/Library/Caches/skore``.
209+
"""
210+
mode, name, plugin, parameters = Project.__setup_plugin(name)
211+
175212
self.__mode = mode
176213
self.__name = name
177-
self.__project = PLUGINS[mode].load()(**kwargs)
214+
self.__project = plugin(**(kwargs | parameters))
178215

179216
@property
180217
def mode(self):
@@ -218,5 +255,39 @@ def put(self, key: str, report: EstimatorReport):
218255
def __repr__(self) -> str: # noqa: D105
219256
return self.__project.__repr__()
220257

258+
@staticmethod
259+
def delete(name: str, **kwargs):
260+
r"""
261+
Delete a project.
262+
263+
Parameters
264+
----------
265+
name : str
266+
The name of the project:
267+
268+
- if the ``name`` takes the form of the URI ``hub://<tenant>/<name>``, the
269+
project is configured to communicate with the ``skore hub``,
270+
- otherwise, the project is configured to communicate with a local storage,
271+
on the user machine.
272+
**kwargs : dict
273+
Extra keyword arguments passed to the project, depending on its mode.
274+
275+
workspace : Path, mode:local only.
276+
The directory where the local project is persisted.
277+
278+
| The workspace can be shared between all the projects.
279+
| The workspace can be set using kwargs or the environment variable
280+
``SKORE_WORKSPACE``.
281+
| If not, it will be by default set to a ``skore/`` directory in the
282+
USER cache directory:
283+
284+
- on Windows, usually ``C:\Users\%USER%\AppData\Local\skore``,
285+
- on Linux, usually ``${HOME}/.cache/skore``,
286+
- on macOS, usually ``${HOME}/Library/Caches/skore``.
287+
"""
288+
_, _, plugin, parameters = Project.__setup_plugin(name)
289+
290+
return plugin.delete(**(kwargs | parameters))
291+
221292

222293
_register_accessor("reports", Project)(_ReportsAccessor)

skore/tests/unit/project/test_project.py

Lines changed: 88 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,71 +15,81 @@
1515
from importlib.metadata import EntryPoint, EntryPoints
1616

1717

18-
class FakeLocalProject(Mock):
19-
def __init__(self, *args, **kwargs):
20-
super().__init__(constructor_args=args, constructor_kwargs=kwargs)
21-
22-
23-
class FakeHubProject(Mock):
24-
def __init__(self, *args, **kwargs):
25-
super().__init__(constructor_args=args, constructor_kwargs=kwargs)
18+
class FakeEntryPoint(EntryPoint):
19+
def load(self):
20+
return self.value
21+
22+
23+
@fixture
24+
def FakeLocalProject():
25+
return Mock()
26+
27+
28+
@fixture
29+
def FakeHubProject():
30+
return Mock()
31+
32+
33+
@fixture(autouse=True)
34+
def monkeypatch_entrypoints(monkeypatch, request, FakeLocalProject, FakeHubProject):
35+
monkeypatch.setattr(
36+
"skore.project.project.entry_points",
37+
lambda **kwargs: EntryPoints(
38+
[
39+
FakeEntryPoint(
40+
name="local",
41+
value=FakeLocalProject,
42+
group="skore.plugins.project",
43+
),
44+
FakeEntryPoint(
45+
name="hub",
46+
value=FakeHubProject,
47+
group="skore.plugins.project",
48+
),
49+
]
50+
),
51+
)
52+
53+
54+
@fixture(scope="module")
55+
def regression():
56+
X, y = make_regression(random_state=42)
57+
X_train, X_test, y_train, y_test = train_test_split(
58+
X, y, test_size=0.2, random_state=42
59+
)
60+
61+
return EstimatorReport(
62+
LinearRegression(),
63+
X_train=X_train,
64+
y_train=y_train,
65+
X_test=X_test,
66+
y_test=y_test,
67+
)
2668

2769

2870
class TestProject:
29-
@fixture(autouse=True)
30-
def monkeypatch_entrypoints(self, monkeypatch, request):
31-
monkeypatch.setattr(
32-
"skore.project.project.entry_points",
33-
lambda **kwargs: EntryPoints(
34-
[
35-
EntryPoint(
36-
name="local",
37-
value=f"{__name__}:FakeLocalProject",
38-
group="skore.plugins.project",
39-
),
40-
EntryPoint(
41-
name="hub",
42-
value=f"{__name__}:FakeHubProject",
43-
group="skore.plugins.project",
44-
),
45-
]
46-
),
47-
)
48-
49-
@fixture(scope="class")
50-
def regression(self):
51-
X, y = make_regression(random_state=42)
52-
X_train, X_test, y_train, y_test = train_test_split(
53-
X, y, test_size=0.2, random_state=42
54-
)
55-
56-
return EstimatorReport(
57-
LinearRegression(),
58-
X_train=X_train,
59-
y_train=y_train,
60-
X_test=X_test,
61-
y_test=y_test,
62-
)
63-
64-
def test_init_local(self):
65-
project = Project("<name>")
71+
def test_init_local(self, FakeLocalProject):
72+
project = Project("<name>", workspace="<workspace>")
6673

6774
assert isinstance(project, Project)
6875
assert project._Project__mode == "local"
6976
assert project._Project__name == "<name>"
70-
assert isinstance(project._Project__project, FakeLocalProject)
71-
assert not project._Project__project.constructor_args
72-
assert project._Project__project.constructor_kwargs == {"name": "<name>"}
77+
assert FakeLocalProject.called
78+
assert not FakeLocalProject.call_args.args
79+
assert FakeLocalProject.call_args.kwargs == {
80+
"name": "<name>",
81+
"workspace": "<workspace>",
82+
}
7383

74-
def test_init_hub(self):
84+
def test_init_hub(self, FakeHubProject):
7585
project = Project("hub://<tenant>/<name>")
7686

7787
assert isinstance(project, Project)
7888
assert project._Project__mode == "hub"
7989
assert project._Project__name == "<name>"
80-
assert isinstance(project._Project__project, FakeHubProject)
81-
assert not project._Project__project.constructor_args
82-
assert project._Project__project.constructor_kwargs == {
90+
assert FakeHubProject.called
91+
assert not FakeHubProject.call_args.args
92+
assert FakeHubProject.call_args.kwargs == {
8393
"tenant": "<tenant>",
8494
"name": "<name>",
8595
}
@@ -125,12 +135,12 @@ def test_name(self):
125135
assert Project("<name>").name == "<name>"
126136
assert Project("hub://<tenant>/<name>").name == "<name>"
127137

128-
def test_put(self, regression):
138+
def test_put(self, regression, FakeLocalProject):
129139
project = Project("<name>")
130140

131141
project.put("<key>", regression)
132142

133-
assert isinstance(project._Project__project, FakeLocalProject)
143+
assert FakeLocalProject.called
134144
assert project._Project__project.put.called
135145
assert not project._Project__project.put.call_args.args
136146
assert project._Project__project.put.call_args.kwargs == {
@@ -152,12 +162,12 @@ def test_reports(self):
152162
assert hasattr(project.reports, "get")
153163
assert hasattr(project.reports, "metadata")
154164

155-
def test_reports_get(self):
165+
def test_reports_get(self, FakeLocalProject):
156166
project = Project("<name>")
157167

158168
project.reports.get("<id>")
159169

160-
assert isinstance(project._Project__project, FakeLocalProject)
170+
assert FakeLocalProject.called
161171
assert project._Project__project.reports.get.called
162172
assert project._Project__project.reports.get.call_args.args == ("<id>",)
163173
assert not project._Project__project.reports.get.call_args.kwargs
@@ -195,3 +205,25 @@ def test_reports_metadata(self):
195205
def test_repr(self):
196206
project = Project("<name>")
197207
assert repr(project) == repr(project._Project__project)
208+
209+
def test_delete_local(self, FakeLocalProject):
210+
Project.delete("<name>", workspace="<workspace>")
211+
212+
assert not FakeLocalProject.called
213+
assert FakeLocalProject.delete.called
214+
assert not FakeLocalProject.delete.call_args.args
215+
assert FakeLocalProject.delete.call_args.kwargs == {
216+
"name": "<name>",
217+
"workspace": "<workspace>",
218+
}
219+
220+
def test_delete_hub(self, FakeHubProject):
221+
Project.delete("hub://<tenant>/<name>")
222+
223+
assert not FakeHubProject.called
224+
assert FakeHubProject.delete.called
225+
assert not FakeHubProject.delete.call_args.args
226+
assert FakeHubProject.delete.call_args.kwargs == {
227+
"tenant": "<tenant>",
228+
"name": "<name>",
229+
}

0 commit comments

Comments
 (0)