Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Make open to spin-off UI server and to be used in CLI and notebook #1157

Merged
merged 64 commits into from
Jan 30, 2025

Conversation

glemaitre
Copy link
Member

@glemaitre glemaitre commented Jan 19, 2025

closes #1002

This PR proposed to resolve the issue discussed in #1002:

  • open should provide the possibility to spin-off a UI server
  • open can therefore be used in the notebook and with the CLI
  • kill to kill all alive servers (normally we tested that we should not have some case or zombie processes).

So in short, we allow for the following:

import skore

project = skore.open("my_project")
project.shutdown_web_ui()

Implementation details

We launch the UI in a separate process. Why process: if we would attach to the async loop for the notebook or run in a separate thread from the main process, we might get into trouble when this main process starts to execute large computation making the UI unresponsive.

So we are starting a new process with the webserver inside. For the moment, we expect to have 1 skore project associated with 1 skore UI server. So we don't allow to spin a new webserver if one already exist and is associated with a running skore project. Instead, we provide the link to the server.

Also, we have a rejoin mechanism: if a project was closed but the UI was not, then when opening a project, we reattach the web server to it.

Finally, we found with @rouk1 that you have the following use-cases:

  • CI/CD: you want serve=False when calling open
  • standalone script: You might be interested in serve=True and in this case, you most probably want to keep the webs server alive (so deamon + atexit is not what we want)
  • jupyter notebook: You might be interested in serve=True and you will be interested to have access to the UI the time that the notebook is alive. The current setup will kill the web server.

So we added a keep_alive option that detect if you are in a specific environment and choose a sensible default but it can be easily overwritten.

Notes

Some of the covering is not really working with the multiprocessing because we explicitly check for the console message in the test and the coverage mentioned that we don't cover it.

TODO

  • write tests for the keep_alive option
  • write tests for the kill cli

Copy link
Contributor

github-actions bot commented Jan 19, 2025

Coverage

Coverage Report for backend
FileStmtsMissCoverMissing
venv/lib/python3.12/site-packages/skore
   __init__.py140100% 
   __main__.py880%3–19
   exceptions.py440%4–23
venv/lib/python3.12/site-packages/skore/cli
   __init__.py550%3–8
   cli.py22220%3–70
   color_format.py49490%3–116
venv/lib/python3.12/site-packages/skore/persistence
   __init__.py00100% 
venv/lib/python3.12/site-packages/skore/persistence/item
   __init__.py59491%89, 100–103
   altair_chart_item.py24193%15
   cross_validation_reporter_item.py1091289%28–41, 125–126, 259, 262
   item.py24196%86
   matplotlib_figure_item.py33195%18
   media_item.py240100% 
   numpy_array_item.py29194%16
   pandas_dataframe_item.py31194%14
   pandas_series_item.py31194%14
   pickle_item.py330100% 
   pillow_image_item.py32194%16
   plotly_figure_item.py25193%15
   polars_dataframe_item.py29194%14
   polars_series_item.py24193%14
   primitive_item.py25291%13–15
   sklearn_base_estimator_item.py31194%15
   skrub_table_report_item.py10186%11
venv/lib/python3.12/site-packages/skore/persistence/repository
   __init__.py30100% 
   item_repository.py59591%15–16, 202–203, 226
   view_repository.py19286%9–10
venv/lib/python3.12/site-packages/skore/persistence/storage
   __init__.py40100% 
   abstract_storage.py220100% 
   disk_cache_storage.py33195%44
   in_memory_storage.py200100% 
venv/lib/python3.12/site-packages/skore/persistence/view
   __init__.py20100% 
   view.py50100% 
venv/lib/python3.12/site-packages/skore/project
   __init__.py30100% 
   _launch.py150199%278
   _open.py90100% 
   project.py71199%250
venv/lib/python3.12/site-packages/skore/sklearn
   __init__.py50100% 
   _base.py140497%91, 94, 168–>173, 183–184
   find_ml_task.py45195%71–>87, 80–>87, 86
   types.py20100% 
venv/lib/python3.12/site-packages/skore/sklearn/_cross_validation
   __init__.py60100% 
   metrics_accessor.py1630100% 
   report.py870100% 
venv/lib/python3.12/site-packages/skore/sklearn/_estimator
   __init__.py60100% 
   metrics_accessor.py265497%149–158, 183–>236, 191, 434–>437, 783–>786
   report.py120099%213–>219, 221–>223
   utils.py11110%1–19
venv/lib/python3.12/site-packages/skore/sklearn/_plot
   __init__.py40100% 
   precision_recall_curve.py121198%229–>246, 317
   prediction_error.py970100% 
   roc_curve.py1280100% 
   utils.py880100% 
venv/lib/python3.12/site-packages/skore/sklearn/cross_validation
   __init__.py20100% 
   cross_validation_helpers.py47490%104–>136, 123–126
   cross_validation_reporter.py35195%187
venv/lib/python3.12/site-packages/skore/sklearn/cross_validation/plots
   __init__.py00100% 
   compare_scores_plot.py292416%10, 28–116
   timing_plot.py292417%10, 26–123
venv/lib/python3.12/site-packages/skore/sklearn/train_test_split
   __init__.py00100% 
   train_test_split.py36294%16–17
venv/lib/python3.12/site-packages/skore/sklearn/train_test_split/warning
   __init__.py80100% 
   high_class_imbalance_too_few_examples_warning.py17378%16–18, 80
   high_class_imbalance_warning.py18288%16–18
   random_state_unset_warning.py11187%15
   shuffle_true_warning.py9091%44–>exit
   stratify_is_set_warning.py11187%15
   time_based_column_warning.py22189%17, 69–>exit
   train_test_split_warning.py5180%21
venv/lib/python3.12/site-packages/skore/ui
   __init__.py00100% 
   app.py32320%3–83
   dependencies.py440%3–10
   project_routes.py62620%3–145
   server.py17170%3–40
venv/lib/python3.12/site-packages/skore/utils
   __init__.py60100% 
   _accessor.py70100% 
   _environment.py26097%29–>34
   _logger.py21484%14–18
   _patch.py11546%19–35
   _progress_bar.py300100% 
   _show_versions.py310100% 
TOTAL288933788% 

Tests Skipped Failures Errors Time
612 3 💤 0 ❌ 0 🔥 2m 37s ⏱️

Copy link
Contributor

github-actions bot commented Jan 23, 2025

Documentation preview @ e1cc277

@rouk1 rouk1 marked this pull request as draft January 24, 2025 09:25
Copy link
Contributor

@rouk1 rouk1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested in jupyter, vscode interactive, and script.

jupyter: works well
vscode interactive: works well except log orders
script: works well except log orders

exemple of strange log orders

──────────────────────────────────────── skore ─────────────────────────────────────────
Project file '/Users/rouk1/dev/skore/project.skore' was successfully created.
──────────────────────────────────────── skore ─────────────────────────────────────────
Project file '/Users/rouk1/dev/skore/project.skore' was successfully created.
─────────────────────────────────────── skore-UI ───────────────────────────────────────
Server is already running at http://localhost:22140
  Processing cross-validation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
  for Lasso                                                                
Processing column   3 / 3
Processing column   8 / 8
yeah
─────────────────────────────────────── skore-UI ───────────────────────────────────────
Running skore UI from 'project.skore' at URL http://localhost:22140 (Press CTRL+C to 
quit)
^C─────────────────────────────────────── skore-UI ───────────────────────────────────────
Server that was running at http://localhost:22140 has been closed gracefully
  Processing cross-validation ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100%
  for Lasso                                                                
Processing column   3 / 3
Processing column   8 / 8
yeah

skore/src/skore/cli/cli.py Outdated Show resolved Hide resolved
skore/src/skore/cli/cli.py Outdated Show resolved Hide resolved
skore/src/skore/cli/cli.py Show resolved Hide resolved
skore/src/skore/project/project.py Outdated Show resolved Hide resolved
skore/src/skore/ui/app.py Outdated Show resolved Hide resolved
skore/src/skore/ui/project_routes.py Show resolved Hide resolved
Copy link
Contributor

@rouk1 rouk1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works fine in a interactive and in jupyter, but when running it from a script the keep_alive argument seems ignored.

# %%
from pathlib import Path

import skore

repo_root = Path(__file__).resolve().parent.parent
project_path = repo_root / "mini.skore"
p = skore.open(project_path, overwrite=True, keep_alive=True)
p.put("markdown", "# yo markdown\n- a\n- b")
p.put("dict", {"a": "zertyuio"})


# %%
p.shutdown_web_ui()

Here is the output I get (I did not hit CTRL+C)

(venv) ➜  skore git:(is/1002) python scratch/mini.py
──────────────────────────────────────── skore ─────────────────────────────────────────
Project file '/Users/rouk1/dev/skore/mini.skore' was successfully created.
─────────────────────────────────────── skore-UI ───────────────────────────────────────
Running skore UI from 'mini.skore' at URL http://localhost:22140
─────────────────────────────────────── skore-UI ───────────────────────────────────────
Server that was running at http://localhost:22140 has been closed gracefully
Press Ctrl+C to stop the server...
(venv) ➜  skore git:(is/1002) 

Sorry if that was not ready for re-review : )

skore/src/skore/ui/server.py Outdated Show resolved Hide resolved
]

process = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you need to use /dev/null for stdin, stdout and stderr to detach the server process from the parent process and make it survive after jupyter kernel restart or script ending (daemon-like process)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I manually handle it:

        if keep_alive:
            atexit.register(block_before_cleanup, project, process)
        else:
            atexit.register(cleanup_server, project)

In this case, I can rejoin the server process and request a KeyboardInterrupt to cleanup. I find it explicit.

make it survive after jupyter kernel restart or script ending

I would expect this behaviour for the script but it was not my first thought for jupyter: I would expect that when restarting the kernel, I'll go and rerun a set of cell and I'll re-execute the skore.open that is going to restart the server.

But I'm open to changes on this side.

@rouk1
Copy link
Contributor

rouk1 commented Jan 28, 2025

This works fine in a interactive and in jupyter, but when running it from a script the keep_alive argument seems ignored.

# %%
from pathlib import Path

import skore

repo_root = Path(__file__).resolve().parent.parent
project_path = repo_root / "mini.skore"
p = skore.open(project_path, overwrite=True, keep_alive=True)
p.put("markdown", "# yo markdown\n- a\n- b")
p.put("dict", {"a": "zertyuio"})


# %%
p.shutdown_web_ui()

Here is the output I get (I did not hit CTRL+C)

(venv) ➜  skore git:(is/1002) python scratch/mini.py
──────────────────────────────────────── skore ─────────────────────────────────────────
Project file '/Users/rouk1/dev/skore/mini.skore' was successfully created.
─────────────────────────────────────── skore-UI ───────────────────────────────────────
Running skore UI from 'mini.skore' at URL http://localhost:22140
─────────────────────────────────────── skore-UI ───────────────────────────────────────
Server that was running at http://localhost:22140 has been closed gracefully
Press Ctrl+C to stop the server...
(venv) ➜  skore git:(is/1002) 

Sorry if that was not ready for re-review : )

My bad it works fine in script aswell when you do not stop manually the server.

Copy link
Member Author

@glemaitre glemaitre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK @rouk1 I did not change anything regarding the implementation but I added more tests for _environment.py or server.py and the kill CLI function.

I remove the launch and replace it open.

I think we can make a follow up PR just for the documentation.

Copy link
Contributor

@rouk1 rouk1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice work 💪🏻

I tested it in jupyter, interactive vscode and scritps and it worked very well.
Just added one comment that may (or not) lead to a follow up issue.

LGTM !

skore/src/skore/ui/server.py Show resolved Hide resolved
@@ -79,7 +79,7 @@ def format_help(self):

# Color the subcommands in cyan
help_text = re.sub(
r"(?<=\s)(launch|create|quickstart)(?=\s+)",
r"(?<=\s)(create|open|kill)(?=\s+)",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
r"(?<=\s)(create|open|kill)(?=\s+)",
r"(?<=\s)(open|kill)(?=\s+)",

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: create doesn't exist anymore.

Copy link
Collaborator

@thomass-dev thomass-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huge work, thanks @glemaitre and @rouk1 !

@thomass-dev thomass-dev merged commit eafd8cb into probabl-ai:main Jan 30, 2025
18 checks passed
@rouk1
Copy link
Contributor

rouk1 commented Jan 31, 2025

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

skore.show_activity() a simple helper function to get users started
4 participants