Repository: probabl-ai/skore

Issue:
Add a `skore.create_or_load`
### Is your feature request related to a problem? Please describe.

When iterating on my modeling, I might often rerun the same notebook, sometimes dropping/removing entire intermediary or debug runs in skore when I don't need them (by deleting the project on my disk). So, I often switch between `skore.create()` and `skore.load()`.

### Describe the solution you'd like

Instead, I'd rather have a single `skore.create_or_load()` function that I could reuse all the time.

### Describe alternatives you've considered, if relevant

_No response_

### Additional context

_No response_

Discussion:
github-actions[bot]: <!-- Pytest Coverage Comment: coverage-skore -->
<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/README.md"><img alt="Coverage" src="https://img.shields.io/badge/Coverage-80%25-green.svg" /></a><details><summary>pytest coverage report </summary><table><tr><th>File</th><th>Stmts</th><th>Miss</th><th>Cover</th><th>Missing</th></tr><tbody><tr><td colspan="5"><b>src/skore</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/__init__.py">\_\_init\_\_.py</a></td><td>12</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/__main__.py">\_\_main\_\_.py</a></td><td>8</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/__main__.py#L 80%"> 80%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/exceptions.py">exceptions.py</a></td><td>3</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td colspan="5"><b>src/skore/cli</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/cli/__init__.py">\_\_init\_\_.py</a></td><td>5</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/cli/cli.py">cli.py</a></td><td>32</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/cli/launch_dashboard.py">launch_dashboard.py</a></td><td>26</td><td>15</td><td>0</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/cli/launch_dashboard.py#L 39%"> 39%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/cli/quickstart_command.py">quickstart_command.py</a></td><td>14</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td colspan="5"><b>src/skore/externals</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/externals/__init__.py">\_\_init\_\_.py</a></td><td>0</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/externals/_sklearn_compat.py">_sklearn_compat.py</a></td><td>220</td><td>183</td><td>4</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/externals/_sklearn_compat.py#L 15%"> 15%</a></td></tr><tr><td colspan="5"><b>src/skore/item</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/__init__.py">\_\_init\_\_.py</a></td><td>21</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/cross_validation_item.py">cross_validation_item.py</a></td><td>127</td><td>10</td><td>2</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/cross_validation_item.py#L 92%"> 92%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/item.py">item.py</a></td><td>41</td><td>13</td><td>0</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/item.py#L 68%"> 68%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/item_repository.py">item_repository.py</a></td><td>42</td><td>2</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/item_repository.py#L 93%"> 93%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/media_item.py">media_item.py</a></td><td>70</td><td>4</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/media_item.py#L 94%"> 94%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/numpy_array_item.py">numpy_array_item.py</a></td><td>25</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/numpy_array_item.py#L 93%"> 93%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/pandas_dataframe_item.py">pandas_dataframe_item.py</a></td><td>34</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/pandas_dataframe_item.py#L 95%"> 95%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/pandas_series_item.py">pandas_series_item.py</a></td><td>34</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/pandas_series_item.py#L 95%"> 95%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/polars_dataframe_item.py">polars_dataframe_item.py</a></td><td>32</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/polars_dataframe_item.py#L 94%"> 94%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/polars_series_item.py">polars_series_item.py</a></td><td>27</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/polars_series_item.py#L 94%"> 94%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/primitive_item.py">primitive_item.py</a></td><td>27</td><td>2</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/primitive_item.py#L 92%"> 92%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/sklearn_base_estimator_item.py">sklearn_base_estimator_item.py</a></td><td>33</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/sklearn_base_estimator_item.py#L 95%"> 95%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/skrub_table_report_item.py">skrub_table_report_item.py</a></td><td>10</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/item/skrub_table_report_item.py#L 86%"> 86%</a></td></tr><tr><td colspan="5"><b>src/skore/persistence</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/persistence/__init__.py">\_\_init\_\_.py</a></td><td>0</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/persistence/abstract_storage.py">abstract_storage.py</a></td><td>22</td><td>1</td><td>0</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/persistence/abstract_storage.py#L 95%"> 95%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/persistence/disk_cache_storage.py">disk_cache_storage.py</a></td><td>33</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/persistence/disk_cache_storage.py#L 95%"> 95%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/persistence/in_memory_storage.py">in_memory_storage.py</a></td><td>20</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td colspan="5"><b>src/skore/project</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/project/__init__.py">\_\_init\_\_.py</a></td><td>5</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/project/create.py">create.py</a></td><td>52</td><td>8</td><td>0</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/project/create.py#L 88%"> 88%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/project/load.py">load.py</a></td><td>23</td><td>3</td><td>0</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/project/load.py#L 89%"> 89%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/project/open.py">open.py</a></td><td>14</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/project/project.py">project.py</a></td><td>64</td><td>4</td><td>4</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/project/project.py#L 91%"> 91%</a></td></tr><tr><td colspan="5"><b>src/skore/sklearn</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/__init__.py">\_\_init\_\_.py</a></td><td>3</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/find_ml_task.py">find_ml_task.py</a></td><td>35</td><td>3</td><td>2</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/find_ml_task.py#L 89%"> 89%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/types.py">types.py</a></td><td>2</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td colspan="5"><b>src/skore/sklearn/cross_validation</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/__init__.py">\_\_init\_\_.py</a></td><td>2</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/cross_validation_helpers.py">cross_validation_helpers.py</a></td><td>41</td><td>4</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/cross_validation_helpers.py#L 89%"> 89%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/cross_validation_reporter.py">cross_validation_reporter.py</a></td><td>37</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/cross_validation_reporter.py#L 95%"> 95%</a></td></tr><tr><td colspan="5"><b>src/skore/sklearn/cross_validation/plots</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/plots/__init__.py">\_\_init\_\_.py</a></td><td>0</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/plots/compare_scores_plot.py">compare_scores_plot.py</a></td><td>29</td><td>1</td><td>2</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/plots/compare_scores_plot.py#L 92%"> 92%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/plots/timing_normalized_plot.py">timing_normalized_plot.py</a></td><td>29</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/plots/timing_normalized_plot.py#L 94%"> 94%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/plots/timing_plot.py">timing_plot.py</a></td><td>29</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/cross_validation/plots/timing_plot.py#L 94%"> 94%</a></td></tr><tr><td colspan="5"><b>src/skore/sklearn/train_test_split</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/__init__.py">\_\_init\_\_.py</a></td><td>0</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/train_test_split.py">train_test_split.py</a></td><td>34</td><td>2</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/train_test_split.py#L 94%"> 94%</a></td></tr><tr><td colspan="5"><b>src/skore/sklearn/train_test_split/warning</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/__init__.py">\_\_init\_\_.py</a></td><td>8</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/high_class_imbalance_too_few_examples_warning.py">high_class_imbalance_too_few_examples_warning.py</a></td><td>17</td><td>3</td><td>2</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/high_class_imbalance_too_few_examples_warning.py#L 78%"> 78%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/high_class_imbalance_warning.py">high_class_imbalance_warning.py</a></td><td>18</td><td>2</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/high_class_imbalance_warning.py#L 88%"> 88%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/random_state_unset_warning.py">random_state_unset_warning.py</a></td><td>11</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/random_state_unset_warning.py#L 87%"> 87%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/shuffle_true_warning.py">shuffle_true_warning.py</a></td><td>9</td><td>0</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/shuffle_true_warning.py#L 91%"> 91%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/stratify_is_set_warning.py">stratify_is_set_warning.py</a></td><td>11</td><td>1</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/stratify_is_set_warning.py#L 87%"> 87%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/time_based_column_warning.py">time_based_column_warning.py</a></td><td>22</td><td>1</td><td>2</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/time_based_column_warning.py#L 89%"> 89%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/train_test_split_warning.py">train_test_split_warning.py</a></td><td>5</td><td>1</td><td>0</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/sklearn/train_test_split/warning/train_test_split_warning.py#L 80%"> 80%</a></td></tr><tr><td colspan="5"><b>src/skore/ui</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/ui/__init__.py">\_\_init\_\_.py</a></td><td>0</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/ui/app.py">app.py</a></td><td>25</td><td>5</td><td>2</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/ui/app.py#L 71%"> 71%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/ui/dependencies.py">dependencies.py</a></td><td>7</td><td>1</td><td>0</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/ui/dependencies.py#L 86%"> 86%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/ui/project_routes.py">project_routes.py</a></td><td>50</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td colspan="5"><b>src/skore/utils</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/utils/__init__.py">\_\_init\_\_.py</a></td><td>0</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/utils/_logger.py">_logger.py</a></td><td>21</td><td>4</td><td>0</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/utils/_logger.py#L 84%"> 84%</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/utils/_show_versions.py">_show_versions.py</a></td><td>29</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td colspan="5"><b>src/skore/view</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/view/__init__.py">\_\_init\_\_.py</a></td><td>0</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/view/view.py">view.py</a></td><td>5</td><td>0</td><td>100%</td><td>&nbsp;</td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/view/view_repository.py">view_repository.py</a></td><td>16</td><td>2</td><td>1</td><td><a href="https://github.com/probabl-ai/skore/blob/e41a4e6741be68bf48dcc647756ca54eca11fd19/src/skore/view/view_repository.py#L 83%"> 83%</a></td></tr><tr><td><b>TOTAL</b></td><td><b>1571</b></td><td><b>288</b></td><td><b>80%</b></td><td>&nbsp;</td></tr></tbody></table></details>

| Tests | Skipped | Failures | Errors | Time |
| ----- | ------- | -------- | -------- | ------------------ |
| 225 | 0 :zzz: | 0 :x: | 0 :fire: | 35.602s :stopwatch: |

augustebaum: 
> * I think that open covers all the cases of create and load, hence to avoid confuion for the users those commands may be removed, wdyt ?

`open` currently relies on `create` and `load`; I could make them private instead?


Patch:
diff --git a/examples/getting_started/plot_quick_start.py b/examples/getting_started/plot_quick_start.py
index 871de94..421ca03 100644
--- a/examples/getting_started/plot_quick_start.py
+++ b/examples/getting_started/plot_quick_start.py
@@ -12,7 +12,7 @@ Quick start
 # %%
 import skore
 
-my_project = skore.create("quick_start", overwrite=True)
+my_project = skore.open("quick_start", overwrite=True)
 
 # %%
 # This will create a skore project directory named ``quick_start.skore`` in your
diff --git a/examples/getting_started/plot_skore_product_tour.py b/examples/getting_started/plot_skore_product_tour.py
index 1002db7..a90dfa4 100644
--- a/examples/getting_started/plot_skore_product_tour.py
+++ b/examples/getting_started/plot_skore_product_tour.py
@@ -44,7 +44,7 @@ Skore product tour
 #
 #     import skore
 #
-#     my_project = skore.create("my_project")
+#     my_project = skore.open("my_project")
 #
 #   This would create a skore project directory named ``my_project.skore`` in our
 #   current directory.
@@ -66,7 +66,7 @@ temp_dir_path = Path(temp_dir.name)
 # %%
 import skore
 
-my_project = skore.create("my_project", working_dir=temp_dir_path)
+my_project = skore.open(temp_dir_path / "my_project")
 
 # %%
 # Then, *from our shell* (in the same directory), we can start the UI locally:
diff --git a/examples/getting_started/plot_tracking_items.py b/examples/getting_started/plot_tracking_items.py
index c03c659..11bfe30 100644
--- a/examples/getting_started/plot_tracking_items.py
+++ b/examples/getting_started/plot_tracking_items.py
@@ -30,7 +30,7 @@ temp_dir_path = Path(temp_dir.name)
 # %%
 import skore
 
-my_project = skore.create("my_project", working_dir=temp_dir_path)
+my_project = skore.open(temp_dir_path / "my_project")
 
 # %%
 # Tracking an integer
diff --git a/examples/getting_started/plot_working_with_projects.py b/examples/getting_started/plot_working_with_projects.py
index 880b3ac..0b16b20 100644
--- a/examples/getting_started/plot_working_with_projects.py
+++ b/examples/getting_started/plot_working_with_projects.py
@@ -30,7 +30,7 @@ temp_dir_path = Path(temp_dir.name)
 # %%
 import skore
 
-my_project = skore.create("my_project", working_dir=temp_dir_path)
+my_project = skore.open(temp_dir_path / "my_project")
 
 # %%
 # Storing integers
diff --git a/examples/model_evaluation/plot_cross_validate.py b/examples/model_evaluation/plot_cross_validate.py
index d298538..9422e37 100644
--- a/examples/model_evaluation/plot_cross_validate.py
+++ b/examples/model_evaluation/plot_cross_validate.py
@@ -30,7 +30,7 @@ temp_dir_path = Path(temp_dir.name)
 # %%
 import skore
 
-my_project = skore.create("my_project", working_dir=temp_dir_path)
+my_project = skore.open(temp_dir_path / "my_project")
 
 # %%
 # Cross-validation in scikit-learn
diff --git a/skore/src/skore/__init__.py b/skore/src/skore/__init__.py
index 5fbe81c..1f33543 100644
--- a/skore/src/skore/__init__.py
+++ b/skore/src/skore/__init__.py
@@ -5,14 +5,13 @@ import logging
 from rich.console import Console
 from rich.theme import Theme
 
-from skore.project import Project, create, load
+from skore.project import Project, open
 from skore.sklearn import CrossValidationReporter, train_test_split
 from skore.utils._show_versions import show_versions
 
 __all__ = [
-    "create",
     "CrossValidationReporter",
-    "load",
+    "open",
     "Project",
     "show_versions",
     "train_test_split",
diff --git a/skore/src/skore/cli/cli.py b/skore/src/skore/cli/cli.py
index e63440b..f66eb26 100644
--- a/skore/src/skore/cli/cli.py
+++ b/skore/src/skore/cli/cli.py
@@ -1,12 +1,11 @@
 """CLI for Skore."""
 
 import argparse
-import pathlib
 from importlib.metadata import version
 
 from skore.cli.launch_dashboard import __launch
 from skore.cli.quickstart_command import __quickstart
-from skore.project import create
+from skore.project.create import _create
 
 
 def cli(args: list[str]):
@@ -52,15 +51,6 @@ def cli(args: list[str]):
         help="the name or path of the project to create (default: %(default)s)",
         default="project",
     )
-    parser_create.add_argument(
-        "--working-dir",
-        type=pathlib.Path,
-        help=(
-            "the directory relative to which the project name will be interpreted; "
-            "default is the current working directory (mostly used for testing)"
-        ),
-        default=None,
-    )
     parser_create.add_argument(
         "--overwrite",
         action="store_true",
@@ -81,15 +71,6 @@ def cli(args: list[str]):
         help="the name or path of the project to create (default: %(default)s)",
         default="project",
     )
-    parser_quickstart.add_argument(
-        "--working-dir",
-        type=pathlib.Path,
-        help=(
-            "the directory relative to which the project name will be interpreted; "
-            "default is the current working directory (mostly used for testing)"
-        ),
-        default=None,
-    )
     parser_quickstart.add_argument(
         "--overwrite",
         action="store_true",
@@ -126,16 +107,14 @@ def cli(args: list[str]):
             verbose=parsed_args.verbose,
         )
     elif parsed_args.subcommand == "create":
-        create(
+        _create(
             project_name=parsed_args.project_name,
-            working_dir=parsed_args.working_dir,
             overwrite=parsed_args.overwrite,
             verbose=parsed_args.verbose,
         )
     elif parsed_args.subcommand == "quickstart":
         __quickstart(
             project_name=parsed_args.project_name,
-            working_dir=parsed_args.working_dir,
             overwrite=parsed_args.overwrite,
             port=parsed_args.port,
             open_browser=parsed_args.open_browser,
diff --git a/skore/src/skore/cli/launch_dashboard.py b/skore/src/skore/cli/launch_dashboard.py
index 239e20e..368838c 100644
--- a/skore/src/skore/cli/launch_dashboard.py
+++ b/skore/src/skore/cli/launch_dashboard.py
@@ -9,7 +9,7 @@ import uvicorn
 from fastapi import FastAPI
 
 from skore.cli import logger
-from skore.project import load
+from skore.project import open
 from skore.ui.app import create_app
 from skore.utils._logger import logger_context
 
@@ -36,7 +36,7 @@ def __launch(
     from skore import console  # avoid circular import
 
     with logger_context(logger, verbose):
-        project = load(project_name)
+        project = open(project_name)
 
         @asynccontextmanager
         async def lifespan(app: FastAPI):
diff --git a/skore/src/skore/cli/quickstart_command.py b/skore/src/skore/cli/quickstart_command.py
index 33c849f..e2aa0c3 100644
--- a/skore/src/skore/cli/quickstart_command.py
+++ b/skore/src/skore/cli/quickstart_command.py
@@ -5,14 +5,12 @@ from typing import Union
 
 from skore.cli import logger
 from skore.cli.launch_dashboard import __launch
-from skore.exceptions import ProjectAlreadyExistsError
-from skore.project import create
+from skore.project.create import _create
 from skore.utils._logger import logger_context
 
 
 def __quickstart(
     project_name: Union[str, Path],
-    working_dir: Union[Path, None],
     overwrite: bool,
     port: int,
     open_browser: bool,
@@ -26,14 +24,9 @@ def __quickstart(
     ----------
     project_name : Path-like
         Name of the project to be created, or a relative or absolute path.
-    working_dir : Path or None
-        If ``project_name`` is not an absolute path, it will be considered relative to
-        ``working_dir``. If `project_name` is an absolute path, ``working_dir`` will
-        have no effect. If set to ``None`` (the default), ``working_dir`` will be re-set
-        to the current working directory.
     overwrite : bool
         If ``True``, overwrite an existing project with the same name.
-        If ``False``, raise an error if a project with the same name already exists.
+        If ``False``, simply warn that a project already exists.
     port : int
         Port at which to bind the UI server.
     open_browser : bool
@@ -43,22 +36,17 @@ def __quickstart(
     """
     with logger_context(logger, verbose):
         try:
-            create(
+            _create(
                 project_name=project_name,
-                working_dir=working_dir,
                 overwrite=overwrite,
                 verbose=verbose,
             )
-        except ProjectAlreadyExistsError:
+        except FileExistsError:
             logger.info(
                 f"Project file '{project_name}' already exists. Skipping creation step."
             )
 
-        path = (
-            Path(project_name)
-            if working_dir is None
-            else Path(working_dir, project_name)
-        )
+        path = Path(project_name)
 
         __launch(
             project_name=path,
diff --git a/skore/src/skore/exceptions.py b/skore/src/skore/exceptions.py
index ba86f41..8df4456 100644
--- a/skore/src/skore/exceptions.py
+++ b/skore/src/skore/exceptions.py
@@ -16,9 +16,5 @@ class ProjectCreationError(Exception):
     """Project creation failed."""
 
 
-class ProjectAlreadyExistsError(Exception):
-    """A project with this name already exists."""
-
-
 class ProjectPermissionError(Exception):
     """Permissions in the directory do not allow creating a file."""
diff --git a/skore/src/skore/project/__init__.py b/skore/src/skore/project/__init__.py
index 883fb8b..be19785 100644
--- a/skore/src/skore/project/__init__.py
+++ b/skore/src/skore/project/__init__.py
@@ -1,11 +1,9 @@
 """Alias top level function and class of the project submodule."""
 
-from .create import create
-from .load import load
+from .open import open
 from .project import Project
 
 __all__ = [
-    "create",
-    "load",
+    "open",
     "Project",
 ]
diff --git a/skore/src/skore/project/create.py b/skore/src/skore/project/create.py
index bee8449..43fab56 100644
--- a/skore/src/skore/project/create.py
+++ b/skore/src/skore/project/create.py
@@ -7,11 +7,10 @@ from typing import Optional, Union
 
 from skore.exceptions import (
     InvalidProjectNameError,
-    ProjectAlreadyExistsError,
     ProjectCreationError,
     ProjectPermissionError,
 )
-from skore.project.load import load
+from skore.project.load import _load
 from skore.project.project import Project, logger
 from skore.utils._logger import logger_context
 from skore.view.view import View
@@ -58,9 +57,8 @@ def _validate_project_name(project_name: str) -> tuple[bool, Optional[Exception]
     return True, None
 
 
-def create(
+def _create(
     project_name: Union[str, Path],
-    working_dir: Optional[Path] = None,
     overwrite: bool = False,
     verbose: bool = False,
 ) -> Project:
@@ -69,12 +67,8 @@ def create(
     Parameters
     ----------
     project_name : Path-like
-        Name of the project to be created, or a relative or absolute path.
-    working_dir : Path or None
-        If ``project_name`` is not an absolute path, it will be considered relative to
-        ``working_dir``. If ``project_name`` is an absolute path, ``working_dir`` will
-        have no effect. If set to ``None`` (the default), ``working_dir`` will be re-set
-        to the current working directory.
+        Name of the project to be created, or a relative or absolute path. If relative,
+        will be interpreted as relative to the current working directory.
     overwrite : bool
         If ``True``, overwrite an existing project with the same name.
         If ``False``, raise an error if a project with the same name already exists.
@@ -105,16 +99,11 @@ def create(
         # If not provided, it will be automatically appended.
         # If project name is an absolute path, we keep that path
 
-        # NOTE: `working_dir` has no effect if `checked_project_name` is absolute
-        if working_dir is None:
-            working_dir = Path.cwd()
-        project_directory = working_dir / (
-            project_path.with_name(checked_project_name + ".skore")
-        )
+        project_directory = project_path.with_name(checked_project_name + ".skore")
 
         if project_directory.exists():
             if not overwrite:
-                raise ProjectAlreadyExistsError(
+                raise FileExistsError(
                     f"Unable to create project file '{project_directory}' because a "
                     "file with that name already exists. Please choose a different "
                     "name or use the --overwrite flag with the CLI or overwrite=True "
@@ -153,7 +142,7 @@ def create(
                 f"Unable to create project file '{views_dir}'."
             ) from e
 
-        p = load(project_directory)
+        p = _load(project_directory)
         p.put_view("default", View(layout=[]))
 
         console.rule("[bold cyan]skore[/bold cyan]")
diff --git a/skore/src/skore/project/load.py b/skore/src/skore/project/load.py
index c789486..80a9bc0 100644
--- a/skore/src/skore/project/load.py
+++ b/skore/src/skore/project/load.py
@@ -13,24 +13,25 @@ class ProjectLoadError(Exception):
     """Failed to load project."""
 
 
-def load(project_name: Union[str, Path]) -> Project:
-    """Load an existing Project given a project name or path."""
-    # Transform a project name to a directory path:
-    # - Resolve relative path to current working directory,
-    # - Check that the file ends with the ".skore" extension,
-    #    - If not provided, it will be automatically appended,
-    # - If project name is an absolute path, we keep that path.
-
+def _load(project_name: Union[str, Path]) -> Project:
+    """Load an existing Project given a project name or path.
+
+    Transforms a project name to a directory path as follows:
+    - Resolves relative path to current working directory,
+    - Checks that the file ends with the ".skore" extension,
+    - If not provided, it will be automatically appended,
+    - If project name is an absolute path, keeps that path.
+    """
     path = Path(project_name).resolve()
 
     if path.suffix != ".skore":
         path = path.parent / (path.name + ".skore")
 
     if not Path(path).exists():
-        raise ProjectLoadError(f"Project '{path}' does not exist: did you create it?")
+        raise FileNotFoundError(f"Project '{path}' does not exist: did you create it?")
 
     try:
-        # FIXME should those hardcoded string be factorized somewhere ?
+        # FIXME: Should those hardcoded strings be factorized somewhere ?
         item_storage = DiskCacheStorage(directory=Path(path) / "items")
         item_repository = ItemRepository(storage=item_storage)
         view_storage = DiskCacheStorage(directory=Path(path) / "views")
diff --git a/skore/src/skore/project/open.py b/skore/src/skore/project/open.py
new file mode 100644
index 0000000..f8da7bd
--- /dev/null
+++ b/skore/src/skore/project/open.py
@@ -0,0 +1,53 @@
+"""Command to open a Project."""
+
+from pathlib import Path
+from typing import Union
+
+from skore.project.create import _create
+from skore.project.load import _load
+from skore.project.project import Project
+
+
+def open(
+    project_path: Union[str, Path] = "project.skore",
+    *,
+    create: bool = True,
+    overwrite: bool = False,
+) -> Project:
+    """Open a project given a project name or path.
+
+    This function creates the project if it does not exist, and it overwrites
+    a pre-existing project if ``overwrite`` is set to ``True``.
+
+    Parameters
+    ----------
+    project_path: Path-like, default="project.skore"
+        The relative or absolute path of the project.
+    create: bool, default=True
+        Create the project if it does not exist.
+    overwrite: bool, default=False
+        Overwrite the project file if it already exists and ``create`` is ``True``.
+        Has no effect otherwise.
+
+    Returns
+    -------
+    Project
+        The opened Project instance.
+
+    Raises
+    ------
+    FileNotFoundError
+        If path is not found and ``create`` is set to ``False``
+    ProjectCreationError
+        If project creation fails for some reason (e.g. if the project path is invalid)
+    """
+    if create and not overwrite:
+        try:
+            return _load(project_path)
+        except FileNotFoundError:
+            return _create(project_path, overwrite=overwrite)
+
+    if not create:
+        return _load(project_path)
+
+    return _create(project_path, overwrite=overwrite)
diff --git a/skore/src/skore/ui/app.py b/skore/src/skore/ui/app.py
index c682eff..eaae88d 100644
--- a/skore/src/skore/ui/app.py
+++ b/skore/src/skore/ui/app.py
@@ -8,7 +8,7 @@ from fastapi.middleware.cors import CORSMiddleware
 from fastapi.staticfiles import StaticFiles
 from starlette.types import Lifespan
 
-from skore.project import Project, load
+from skore.project import Project, open
 from skore.ui.dependencies import get_static_path
 from skore.ui.project_routes import router as project_router
 
@@ -21,7 +21,7 @@ def create_app(
 
     # Give the app access to the project
     if not project:
-        project = load("project.skore")
+        project = open("project.skore")
 
     app.state.project = project
 
diff --git a/sphinx/api.rst b/sphinx/api.rst
index 5113e4b..4c3b253 100644
--- a/sphinx/api.rst
+++ b/sphinx/api.rst
@@ -21,8 +21,7 @@ These functions and classes are meant for managing a Project.
 
     Project
     item.primitive_item.PrimitiveItem
-    create
-    load
+    open
 
 Get assistance when developing ML/DS projects
 ---------------------------------------------


Test patch:
diff --git a/examples/model_evaluation/plot_train_test_split.py b/examples/model_evaluation/plot_train_test_split.py
index 8ade0f3..7a973e0 100644
--- a/examples/model_evaluation/plot_train_test_split.py
+++ b/examples/model_evaluation/plot_train_test_split.py
@@ -30,7 +30,7 @@ temp_dir_path = Path(temp_dir.name)
 # %%
 import skore
 
-my_project = skore.create("my_project", working_dir=temp_dir_path)
+my_project = skore.open(temp_dir_path / "my_project")
 
 # %%
 # Train-test split in scikit-learn
diff --git a/skore/tests/integration/cli/test_create.py b/skore/tests/integration/cli/test_create.py
index 984f226..6e249b3 100644
--- a/skore/tests/integration/cli/test_create.py
+++ b/skore/tests/integration/cli/test_create.py
@@ -1,57 +1,43 @@
-import subprocess
+import os
 
 import pytest
+from skore.cli.cli import cli
+from skore.exceptions import ProjectCreationError
 
 
 def test_create_project_cli_default_argument(tmp_path):
-    completed_process = subprocess.run(
-        f"skore create --working-dir {tmp_path}".split(), capture_output=True
-    )
-    completed_process.check_returncode()
+    os.chdir(tmp_path)
+    cli("create".split())
     assert (tmp_path / "project.skore").exists()
 
 
+def test_create_project_cli_absolute_path(tmp_path):
+    os.chdir(tmp_path)
+    cli(f"create {tmp_path / 'hello.skore'}".split())
+    assert (tmp_path / "hello.skore").exists()
+
+
 def test_create_project_cli_ends_in_skore(tmp_path):
-    completed_process = subprocess.run(
-        f"skore create hello.skore --working-dir {tmp_path}".split(),
-        capture_output=True,
-    )
-    completed_process.check_returncode()
+    os.chdir(tmp_path)
+    cli("create hello.skore".split())
     assert (tmp_path / "hello.skore").exists()
 
 
-def test_create_project_cli_invalid_name(tmp_path):
-    completed_process = subprocess.run(
-        f"skore create hello.txt --working-dir {tmp_path}".split(),
-        capture_output=True,
-    )
-    with pytest.raises(subprocess.CalledProcessError):
-        completed_process.check_returncode()
-    assert b"InvalidProjectNameError" in completed_process.stderr
+def test_create_project_cli_invalid_name():
+    with pytest.raises(ProjectCreationError):
+        cli("create hello.txt".split())
 
 
 def test_create_project_cli_overwrite(tmp_path):
     """Check the behaviour of the `overwrite` flag/parameter."""
-    completed_process = subprocess.run(
-        f"skore create --working-dir {tmp_path}".split(),
-        capture_output=True,
-    )
-    completed_process.check_returncode()
+    os.chdir(tmp_path)
+    cli("create".split())
     assert (tmp_path / "project.skore").exists()
 
     # calling the same command without overwriting should fail
-    completed_process = subprocess.run(
-        f"skore create --working-dir {tmp_path}".split(),
-        capture_output=True,
-    )
-    with pytest.raises(subprocess.CalledProcessError):
-        completed_process.check_returncode()
-    assert b"ProjectAlreadyExistsError" in completed_process.stderr
+    with pytest.raises(FileExistsError):
+        cli("create".split())
 
     # calling the same command with overwriting should succeed
-    completed_process = subprocess.run(
-        f"skore create --working-dir {tmp_path} --overwrite".split(),
-        capture_output=True,
-    )
-    completed_process.check_returncode()
+    cli("create --overwrite".split())
     assert (tmp_path / "project.skore").exists()
diff --git a/skore/tests/integration/cli/test_quickstart.py b/skore/tests/integration/cli/test_quickstart.py
new file mode 100644
index 0000000..ee2eb26
--- /dev/null
+++ b/skore/tests/integration/cli/test_quickstart.py
@@ -0,0 +1,25 @@
+import os
+
+import pytest
+from skore.cli.cli import cli
+
+
+@pytest.fixture
+def fake_launch(monkeypatch):
+    def _fake_launch(project_name, port, open_browser, verbose):
+        pass
+
+    monkeypatch.setattr("skore.cli.quickstart_command.__launch", _fake_launch)
+
+
+def test_quickstart(tmp_path, fake_launch):
+    os.chdir(tmp_path)
+    cli("quickstart".split())
+    assert (tmp_path / "project.skore").exists()
+
+    # calling the same command without overwriting should succeed
+    # (as the creation step is skipped)
+    cli("quickstart".split())
+
+    # calling the same command with overwriting should succeed
+    cli("quickstart --overwrite".split())
diff --git a/skore/tests/unit/cli/test_quickstart.py b/skore/tests/unit/cli/test_quickstart.py
index 81114bd..40c65f0 100644
--- a/skore/tests/unit/cli/test_quickstart.py
+++ b/skore/tests/unit/cli/test_quickstart.py
@@ -1,28 +1,23 @@
-from pathlib import Path
-
 from skore.cli.cli import cli
 
 
-def test_quickstart(monkeypatch):
+def test_quickstart(monkeypatch, tmp_path):
     """`quickstart` passes its arguments down to `create` and `launch`."""
 
     create_project_name = None
-    create_working_dir = None
     create_overwrite = None
     create_verbose = None
 
-    def fake_create(project_name, working_dir, overwrite, verbose):
+    def fake_create(project_name, overwrite, verbose):
         nonlocal create_project_name
-        nonlocal create_working_dir
         nonlocal create_overwrite
         nonlocal create_verbose
 
         create_project_name = project_name
-        create_working_dir = working_dir
         create_overwrite = overwrite
         create_verbose = verbose
 
-    monkeypatch.setattr("skore.cli.quickstart_command.create", fake_create)
+    monkeypatch.setattr("skore.cli.quickstart_command._create", fake_create)
 
     launch_project_name = None
     launch_port = None
@@ -45,23 +40,20 @@ def test_quickstart(monkeypatch):
     cli(
         [
             "quickstart",
-            "my_project.skore",
+            str(tmp_path / "my_project.skore"),
             "--verbose",
             "--overwrite",
-            "--working-dir",
-            "/tmp",
             "--port",
             "888",
             "--no-open-browser",
         ]
     )
 
-    assert create_project_name == "my_project.skore"
-    assert create_working_dir == Path("/tmp")
+    assert create_project_name == str(tmp_path / "my_project.skore")
     assert create_overwrite is True
     assert create_verbose is True
 
-    assert launch_project_name == Path("/tmp/my_project.skore")
+    assert launch_project_name == tmp_path / "my_project.skore"
     assert launch_port == 888
     assert launch_open_browser is False
     assert launch_verbose is True
diff --git a/skore/tests/unit/project/test_load.py b/skore/tests/unit/project/test_load.py
new file mode 100644
index 0000000..06dd4b6
--- /dev/null
+++ b/skore/tests/unit/project/test_load.py
@@ -0,0 +1,32 @@
+import os
+
+import pytest
+from skore.project import Project
+from skore.project.load import _load
+
+
+@pytest.fixture
+def tmp_project_path(tmp_path):
+    """Create a project at `tmp_path` and return its absolute path."""
+    # Project path must end with ".skore"
+    project_path = tmp_path.parent / (tmp_path.name + ".skore")
+    os.mkdir(project_path)
+    os.mkdir(project_path / "items")
+    os.mkdir(project_path / "views")
+    return project_path
+
+
+def test_load_no_project():
+    with pytest.raises(FileNotFoundError):
+        _load("/empty")
+
+
+def test_load_absolute_path(tmp_project_path):
+    p = _load(tmp_project_path)
+    assert isinstance(p, Project)
+
+
+def test_load_relative_path(tmp_project_path):
+    os.chdir(tmp_project_path.parent)
+    p = _load(tmp_project_path.name)
+    assert isinstance(p, Project)
diff --git a/skore/tests/unit/project/test_open.py b/skore/tests/unit/project/test_open.py
new file mode 100644
index 0000000..9f5bc0d
--- /dev/null
+++ b/skore/tests/unit/project/test_open.py
@@ -0,0 +1,93 @@
+import os
+from contextlib import contextmanager
+
+import pytest
+from skore.project import Project, open
+
+
+@pytest.fixture
+def tmp_project_path(tmp_path):
+    """Create a project at `tmp_path` and return its absolute path."""
+    # Project path must end with ".skore"
+    project_path = tmp_path.parent / (tmp_path.name + ".skore")
+    os.mkdir(project_path)
+    os.mkdir(project_path / "items")
+    os.mkdir(project_path / "views")
+    return project_path
+
+
+@contextmanager
+def project_changed(project_path, modified=True):
+    """Assert that the project at `project_path` was changed.
+
+    If `modified` is False, instead assert that it was *not* changed.
+    """
+    (project_path / "my_test_file").write_text("hello")
+    yield
+    if modified:
+        assert not (project_path / "my_test_file").exists()
+    else:
+        assert (project_path / "my_test_file").exists()
+
+
+def test_open_relative_path(tmp_project_path):
+    """If passed a relative path, `open` operates in the current working directory."""
+    os.chdir(tmp_project_path.parent)
+    p = open(tmp_project_path.name, create=False)
+    assert isinstance(p, Project)
+
+
+def test_open_default(tmp_project_path):
+    """If a project already exists, `open` loads it."""
+    with project_changed(tmp_project_path, modified=False):
+        p = open(tmp_project_path)
+        assert isinstance(p, Project)
+
+
+def test_open_default_no_project(tmp_path):
+    """If no project exists, `open` creates it."""
+    with project_changed(tmp_path, modified=False):
+        p = open(tmp_path)
+        assert isinstance(p, Project)
+
+
+def test_open_project_exists_create_true_overwrite_true(tmp_project_path):
+    """If the project exists, and `create` and `overwrite` are set to `True`,
+    `open` overwrites it with a new one."""
+    with project_changed(tmp_project_path):
+        open(tmp_project_path, create=True, overwrite=True)
+
+
+def test_open_project_exists_create_true_overwrite_false(tmp_project_path):
+    with project_changed(tmp_project_path, modified=False):
+        open(tmp_project_path, create=True, overwrite=False)
+
+
+def test_open_project_exists_create_false_overwrite_true(tmp_project_path):
+    p = open(tmp_project_path, create=False, overwrite=True)
+    assert isinstance(p, Project)
+
+
+def test_open_project_exists_create_false_overwrite_false(tmp_project_path):
+    p = open(tmp_project_path, create=False, overwrite=False)
+    assert isinstance(p, Project)
+
+
+def test_open_no_project_create_true_overwrite_true(tmp_path):
+    p = open(tmp_path, create=True, overwrite=True)
+    assert isinstance(p, Project)
+
+
+def test_open_no_project_create_true_overwrite_false(tmp_path):
+    p = open(tmp_path, create=True, overwrite=False)
+    assert isinstance(p, Project)
+
+
+def test_open_no_project_create_false_overwrite_true(tmp_path):
+    with pytest.raises(FileNotFoundError):
+        open(tmp_path, create=False, overwrite=True)
+
+
+def test_open_no_project_create_false_overwrite_false(tmp_path):
+    with pytest.raises(FileNotFoundError):
+        open(tmp_path, create=False, overwrite=False)
diff --git a/skore/tests/unit/test_project.py b/skore/tests/unit/project/test_project.py
similarity index 90%
rename from skore/tests/unit/test_project.py
rename to skore/tests/unit/project/test_project.py
index 88b256e..85cd8db 100644
--- a/skore/tests/unit/test_project.py
+++ b/skore/tests/unit/project/test_project.py
@@ -1,4 +1,3 @@
-import os
 from io import BytesIO
 
 import altair
@@ -14,16 +13,9 @@ from PIL import Image
 from sklearn.ensemble import RandomForestClassifier
 from skore.exceptions import (
     InvalidProjectNameError,
-    ProjectAlreadyExistsError,
     ProjectCreationError,
 )
-from skore.project import (
-    Project,
-    create,
-    load,
-)
-from skore.project.create import _validate_project_name
-from skore.project.load import ProjectLoadError
+from skore.project.create import _create, _validate_project_name
 from skore.view.view import View
 
 
@@ -147,25 +139,6 @@ def test_put_rf_model(in_memory_project, monkeypatch):
     assert isinstance(in_memory_project.get("rf_model"), RandomForestClassifier)
 
 
-def test_load(tmp_path):
-    with pytest.raises(ProjectLoadError):
-        load("/empty")
-
-    # Project path must end with ".skore"
-    project_path = tmp_path.parent / (tmp_path.name + ".skore")
-    os.mkdir(project_path)
-    os.mkdir(project_path / "items")
-    os.mkdir(project_path / "views")
-
-    p = load(project_path)
-    assert isinstance(p, Project)
-
-    # Test without `.skore`
-    os.chdir(tmp_path.parent)
-    p = load(tmp_path.name)
-    assert isinstance(p, Project)
-
-
 def test_put(in_memory_project):
     in_memory_project.put("key1", 1)
     in_memory_project.put("key2", 2)
@@ -323,30 +296,30 @@ def test_validate_project_name(project_name, expected):
 
 @pytest.mark.parametrize("project_name", ["hello", "hello.skore"])
 def test_create_project(project_name, tmp_path):
-    create(project_name, working_dir=tmp_path)
+    _create(tmp_path / project_name)
     assert (tmp_path / "hello.skore").exists()
 
 
 # TODO: If using fixtures in test cases is possible, join this with
 # `test_create_project`
 def test_create_project_absolute_path(tmp_path):
-    create(tmp_path / "hello")
+    _create(tmp_path / "hello")
     assert (tmp_path / "hello.skore").exists()
 
 
 def test_create_project_fails_if_file_exists(tmp_path):
-    create(tmp_path / "hello")
+    _create(tmp_path / "hello")
     assert (tmp_path / "hello.skore").exists()
-    with pytest.raises(ProjectAlreadyExistsError):
-        create(tmp_path / "hello")
+    with pytest.raises(FileExistsError):
+        _create(tmp_path / "hello")
 
 
 def test_create_project_fails_if_permission_denied(tmp_path):
     with pytest.raises(ProjectCreationError):
-        create("/")
+        _create("/")
 
 
 @pytest.mark.parametrize("project_name", ["hello.txt", "%%%", "COM1"])
 def test_create_project_fails_if_invalid_name(project_name, tmp_path):
     with pytest.raises(ProjectCreationError):
-        create(project_name, working_dir=tmp_path)
+        _create(tmp_path / project_name)