From 22f26ccfe444169ade9425e41eb718e5e8744ec8 Mon Sep 17 00:00:00 2001 From: Thomas S Date: Thu, 10 Oct 2024 11:45:37 +0200 Subject: [PATCH] feat: Adapt skore to be compliant on `python >=3.9, <3.13` --- .github/workflows/skore.yml | 5 +- .gitignore | 2 +- CONTRIBUTING.md | 11 +- Makefile | 16 +- skore/pyproject.toml | 14 +- skore/requirements-test.txt | 202 ------------------ skore/requirements-tools.txt | 101 --------- skore/requirements.txt | 84 -------- skore/src/skore/cli/cli.py | 35 ++- skore/src/skore/cli/create_project.py | 7 +- skore/src/skore/cli/launch_dashboard.py | 3 +- skore/src/skore/item/item.py | 10 +- skore/src/skore/project.py | 4 +- skore/src/skore/ui/app.py | 3 +- skore/tests/conftest.py | 4 +- skore/tests/unit/item/test_item_repository.py | 8 +- 16 files changed, 50 insertions(+), 459 deletions(-) delete mode 100644 skore/requirements-test.txt delete mode 100644 skore/requirements-tools.txt delete mode 100644 skore/requirements.txt diff --git a/.github/workflows/skore.yml b/.github/workflows/skore.yml index 8955923c..089cb890 100644 --- a/.github/workflows/skore.yml +++ b/.github/workflows/skore.yml @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ['3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -62,8 +62,7 @@ jobs: python -m build # Install - python -m pip install dist/*.whl --no-dependencies - python -m pip install -r requirements.txt -r requirements-test.txt + wheel=(dist/*.whl); python -m pip install "${wheel}[test]" # Test python -m pytest src/ tests/ diff --git a/.gitignore b/.gitignore index dd066b75..70777318 100644 --- a/.gitignore +++ b/.gitignore @@ -83,7 +83,7 @@ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# .python-version +.python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfd8a38b..6baa701f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,9 +10,9 @@ Bug reports are welcome, especially those reported with [short, self-contained, ### Quick start -You'll need Python>=3.12 to build the backend and Node>=20 to build the skore-ui. Then, you can install dependencies and run the UI with: +You'll need `python >=3.9, <3.13` to build the backend and Node>=20 to build the skore-ui. Then, you can install dependencies and run the UI with: ```sh -make install +make install-skore make build-skore-ui make serve-ui ``` @@ -24,7 +24,7 @@ If you want to contribute, please continue with the three other sections. Install backend dependencies with ```sh -make install +make install-skore ``` You can run the API server with @@ -32,11 +32,6 @@ You can run the API server with make serve-api ``` -When dependencies are changed in `pyproject.toml` the lockfiles should be updated via [`pip-compile`](https://github.com/jazzband/pip-tools): -```sh -make pip-compile -``` - ### skore-ui Install skore-ui dependencies with diff --git a/Makefile b/Makefile index d71f17f1..f5ec3e9d 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,5 @@ -SKORE_ROOT ?= ".datamander" - -pip-compile: - python -m piptools compile --output-file=skore/requirements.txt skore/pyproject.toml - python -m piptools compile --extra=test --output-file=skore/requirements-test.txt skore/pyproject.toml - python -m piptools compile --extra=tools --output-file=skore/requirements-tools.txt skore/pyproject.toml - install-skore: - python -m pip install \ - -e skore/ \ - -r skore/requirements.txt \ - -r skore/requirements-test.txt \ - -r skore/requirements-tools.txt - + python -m pip install -e './skore[test]' pre-commit install build-skore-ui: @@ -29,7 +17,7 @@ build-skore-ui: mv skore-ui/dist/ skore/src/skore/ui/static serve-skore-ui: - SKORE_ROOT=$(SKORE_ROOT) python -m uvicorn \ + python -m uvicorn \ --factory skore.ui.app:create_app \ --reload --reload-dir skore/src \ --host 0.0.0.0 \ diff --git a/skore/pyproject.toml b/skore/pyproject.toml index 2977916b..cbe6f60a 100644 --- a/skore/pyproject.toml +++ b/skore/pyproject.toml @@ -6,10 +6,8 @@ dynamic = [ "readme", "version" ] -requires-python = ">=3.11" -maintainers = [ - {name = "skore developers", email="skore@signal.probabl.ai"}, -] +requires-python = ">=3.9, <3.13" +maintainers = [{name = "skore developers", email="skore@signal.probabl.ai"}] dependencies = [ "diskcache", "fastapi", @@ -30,8 +28,10 @@ classifiers=[ "Operating System :: Unix", "Operating System :: MacOS", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12" + "Programming Language :: Python :: 3.12", ] [project.urls] @@ -76,10 +76,6 @@ test = [ "scikit-learn", ] -tools = [ - "pip-tools", -] - [tool.pytest.ini_options] addopts = [ "--doctest-modules", diff --git a/skore/requirements-test.txt b/skore/requirements-test.txt deleted file mode 100644 index 46263978..00000000 --- a/skore/requirements-test.txt +++ /dev/null @@ -1,202 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --extra=test --output-file=skore/requirements-test.txt skore/pyproject.toml -# -altair==5.4.1 - # via skore (skore/pyproject.toml) -annotated-types==0.7.0 - # via pydantic -anyio==4.6.0 - # via - # httpx - # starlette -attrs==24.2.0 - # via - # jsonschema - # referencing -certifi==2024.8.30 - # via - # httpcore - # httpx - # requests -cfgv==3.4.0 - # via pre-commit -charset-normalizer==3.3.2 - # via requests -click==8.1.7 - # via uvicorn -contourpy==1.3.0 - # via matplotlib -coverage[toml]==7.6.1 - # via pytest-cov -cycler==0.12.1 - # via matplotlib -diskcache==5.6.3 - # via skore (skore/pyproject.toml) -distlib==0.3.8 - # via virtualenv -fastapi==0.115.0 - # via skore (skore/pyproject.toml) -filelock==3.16.1 - # via - # huggingface-hub - # virtualenv -fonttools==4.54.1 - # via matplotlib -fsspec==2024.9.0 - # via huggingface-hub -h11==0.14.0 - # via - # httpcore - # uvicorn -httpcore==1.0.6 - # via httpx -httpx==0.27.2 - # via skore (skore/pyproject.toml) -huggingface-hub==0.25.1 - # via skops -identify==2.6.1 - # via pre-commit -idna==3.10 - # via - # anyio - # httpx - # requests -iniconfig==2.0.0 - # via pytest -jinja2==3.1.4 - # via altair -joblib==1.4.2 - # via scikit-learn -jsonschema==4.23.0 - # via altair -jsonschema-specifications==2023.12.1 - # via jsonschema -kiwisolver==1.4.7 - # via matplotlib -markdown-it-py==3.0.0 - # via rich -markupsafe==3.0.0 - # via jinja2 -matplotlib==3.9.2 - # via skore (skore/pyproject.toml) -mdurl==0.1.2 - # via markdown-it-py -narwhals==1.9.1 - # via altair -nodeenv==1.9.1 - # via pre-commit -numpy==2.1.2 - # via - # contourpy - # matplotlib - # pandas - # scikit-learn - # scipy -packaging==24.1 - # via - # altair - # huggingface-hub - # matplotlib - # plotly - # pytest - # skops -pandas==2.2.3 - # via skore (skore/pyproject.toml) -pillow==10.4.0 - # via - # matplotlib - # skore (skore/pyproject.toml) -platformdirs==4.3.6 - # via virtualenv -plotly==5.24.1 - # via skore (skore/pyproject.toml) -pluggy==1.5.0 - # via pytest -pre-commit==4.0.0 - # via skore (skore/pyproject.toml) -pydantic==2.9.2 - # via fastapi -pydantic-core==2.23.4 - # via pydantic -pygments==2.18.0 - # via rich -pyparsing==3.1.4 - # via matplotlib -pytest==8.3.3 - # via - # pytest-cov - # pytest-order - # pytest-randomly - # skore (skore/pyproject.toml) -pytest-cov==5.0.0 - # via skore (skore/pyproject.toml) -pytest-order==1.3.0 - # via skore (skore/pyproject.toml) -pytest-randomly==3.15.0 - # via skore (skore/pyproject.toml) -python-dateutil==2.9.0.post0 - # via - # matplotlib - # pandas -pytz==2024.2 - # via pandas -pyyaml==6.0.2 - # via - # huggingface-hub - # pre-commit -referencing==0.35.1 - # via - # jsonschema - # jsonschema-specifications -requests==2.32.3 - # via huggingface-hub -rich==13.9.2 - # via skore (skore/pyproject.toml) -rpds-py==0.20.0 - # via - # jsonschema - # referencing -ruff==0.6.9 - # via skore (skore/pyproject.toml) -scikit-learn==1.5.2 - # via - # skops - # skore (skore/pyproject.toml) -scipy==1.14.1 - # via scikit-learn -six==1.16.0 - # via python-dateutil -skops==0.10.0 - # via skore (skore/pyproject.toml) -sniffio==1.3.1 - # via - # anyio - # httpx -starlette==0.38.6 - # via fastapi -tabulate==0.9.0 - # via skops -tenacity==9.0.0 - # via plotly -threadpoolctl==3.5.0 - # via scikit-learn -tqdm==4.66.5 - # via huggingface-hub -typing-extensions==4.12.2 - # via - # altair - # fastapi - # huggingface-hub - # pydantic - # pydantic-core -tzdata==2024.2 - # via pandas -urllib3==2.2.3 - # via requests -uvicorn==0.31.0 - # via skore (skore/pyproject.toml) -virtualenv==20.26.6 - # via pre-commit diff --git a/skore/requirements-tools.txt b/skore/requirements-tools.txt deleted file mode 100644 index 28cb22ea..00000000 --- a/skore/requirements-tools.txt +++ /dev/null @@ -1,101 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --extra=tools --output-file=skore/requirements-tools.txt skore/pyproject.toml -# -annotated-types==0.7.0 - # via pydantic -anyio==4.6.0 - # via starlette -build==1.2.2.post1 - # via pip-tools -certifi==2024.8.30 - # via requests -charset-normalizer==3.3.2 - # via requests -click==8.1.7 - # via - # pip-tools - # uvicorn -diskcache==5.6.3 - # via skore (skore/pyproject.toml) -fastapi==0.115.0 - # via skore (skore/pyproject.toml) -filelock==3.16.1 - # via huggingface-hub -fsspec==2024.9.0 - # via huggingface-hub -h11==0.14.0 - # via uvicorn -huggingface-hub==0.25.1 - # via skops -idna==3.10 - # via - # anyio - # requests -joblib==1.4.2 - # via scikit-learn -markdown-it-py==3.0.0 - # via rich -mdurl==0.1.2 - # via markdown-it-py -numpy==2.1.2 - # via - # scikit-learn - # scipy -packaging==24.1 - # via - # build - # huggingface-hub - # skops -pip-tools==7.4.1 - # via skore (skore/pyproject.toml) -pydantic==2.9.2 - # via fastapi -pydantic-core==2.23.4 - # via pydantic -pygments==2.18.0 - # via rich -pyproject-hooks==1.2.0 - # via - # build - # pip-tools -pyyaml==6.0.2 - # via huggingface-hub -requests==2.32.3 - # via huggingface-hub -rich==13.9.2 - # via skore (skore/pyproject.toml) -scikit-learn==1.5.2 - # via skops -scipy==1.14.1 - # via scikit-learn -skops==0.10.0 - # via skore (skore/pyproject.toml) -sniffio==1.3.1 - # via anyio -starlette==0.38.6 - # via fastapi -tabulate==0.9.0 - # via skops -threadpoolctl==3.5.0 - # via scikit-learn -tqdm==4.66.5 - # via huggingface-hub -typing-extensions==4.12.2 - # via - # fastapi - # huggingface-hub - # pydantic - # pydantic-core -urllib3==2.2.3 - # via requests -uvicorn==0.31.0 - # via skore (skore/pyproject.toml) -wheel==0.44.0 - # via pip-tools - -# The following packages are considered to be unsafe in a requirements file: -# pip -# setuptools diff --git a/skore/requirements.txt b/skore/requirements.txt deleted file mode 100644 index 6e2aa133..00000000 --- a/skore/requirements.txt +++ /dev/null @@ -1,84 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --output-file=skore/requirements.txt skore/pyproject.toml -# -annotated-types==0.7.0 - # via pydantic -anyio==4.6.0 - # via starlette -certifi==2024.8.30 - # via requests -charset-normalizer==3.3.2 - # via requests -click==8.1.7 - # via uvicorn -diskcache==5.6.3 - # via skore (skore/pyproject.toml) -fastapi==0.115.0 - # via skore (skore/pyproject.toml) -filelock==3.16.1 - # via huggingface-hub -fsspec==2024.9.0 - # via huggingface-hub -h11==0.14.0 - # via uvicorn -huggingface-hub==0.25.1 - # via skops -idna==3.10 - # via - # anyio - # requests -joblib==1.4.2 - # via scikit-learn -markdown-it-py==3.0.0 - # via rich -mdurl==0.1.2 - # via markdown-it-py -numpy==2.1.2 - # via - # scikit-learn - # scipy -packaging==24.1 - # via - # huggingface-hub - # skops -pydantic==2.9.2 - # via fastapi -pydantic-core==2.23.4 - # via pydantic -pygments==2.18.0 - # via rich -pyyaml==6.0.2 - # via huggingface-hub -requests==2.32.3 - # via huggingface-hub -rich==13.9.2 - # via skore (skore/pyproject.toml) -scikit-learn==1.5.2 - # via skops -scipy==1.14.1 - # via scikit-learn -skops==0.10.0 - # via skore (skore/pyproject.toml) -sniffio==1.3.1 - # via anyio -starlette==0.38.6 - # via fastapi -tabulate==0.9.0 - # via skops -threadpoolctl==3.5.0 - # via scikit-learn -tqdm==4.66.5 - # via huggingface-hub -typing-extensions==4.12.2 - # via - # fastapi - # huggingface-hub - # pydantic - # pydantic-core -urllib3==2.2.3 - # via requests -uvicorn==0.31.0 - # via skore (skore/pyproject.toml) diff --git a/skore/src/skore/cli/cli.py b/skore/src/skore/cli/cli.py index 740724ed..72c8f0bb 100644 --- a/skore/src/skore/cli/cli.py +++ b/skore/src/skore/cli/cli.py @@ -65,23 +65,18 @@ def cli(args: list[str]): parsed_args: argparse.Namespace = parser.parse_args(args) - match parsed_args.subcommand: - case None: - parser.print_help() - case "launch": - __launch( - project_name=parsed_args.project_name, - port=parsed_args.port, - open_browser=parsed_args.open_browser, - ) - case "create": - __create( - project_name=parsed_args.project_name, - working_dir=parsed_args.working_dir, - ) - case "quickstart": - __quickstart() - case _: - # `parser.parse_args` raises an error if an unknown subcommand is passed, - # so this case is impossible - return + if parsed_args.subcommand == "launch": + __launch( + project_name=parsed_args.project_name, + port=parsed_args.port, + open_browser=parsed_args.open_browser, + ) + elif parsed_args.subcommand == "create": + __create( + project_name=parsed_args.project_name, + working_dir=parsed_args.working_dir, + ) + elif parsed_args.subcommand == "quickstart": + __quickstart() + else: + parser.print_help() diff --git a/skore/src/skore/cli/create_project.py b/skore/src/skore/cli/create_project.py index 78802615..b754de87 100644 --- a/skore/src/skore/cli/create_project.py +++ b/skore/src/skore/cli/create_project.py @@ -4,6 +4,7 @@ from pathlib import Path from skore.cli import logger +from typing import Optional, Union class ProjectNameTooLong(Exception): @@ -25,7 +26,7 @@ class ImproperProjectName(Exception): """ -def validate_project_name(project_name: str) -> (bool, Exception | None): +def validate_project_name(project_name: str) -> Union[bool, Exception, None]: """Validate the project name (the part before ".skore"). Returns `(True, None)` if validation succeeded and `(False, Exception(...))` @@ -78,7 +79,9 @@ class ProjectPermissionError(Exception): """Permissions in the directory do not allow creating a file.""" -def __create(project_name: str | Path, working_dir: Path | None = None) -> Path: +def __create( + project_name: Union[str, Path], working_dir: Optional[Path] = None +) -> Path: """Create a project file named according to `project_name`. Parameters diff --git a/skore/src/skore/cli/launch_dashboard.py b/skore/src/skore/cli/launch_dashboard.py index 3cce8242..e397319a 100644 --- a/skore/src/skore/cli/launch_dashboard.py +++ b/skore/src/skore/cli/launch_dashboard.py @@ -10,6 +10,7 @@ from skore.cli import logger from skore.project import load from skore.ui.app import create_app +from typing import Union class ProjectNotFound(Exception): @@ -18,7 +19,7 @@ class ProjectNotFound(Exception): project_path: Path -def __launch(project_name: str | Path, port: int, open_browser: bool): +def __launch(project_name: Union[str, Path], port: int, open_browser: bool): """Launch the UI to visualize a project. Parameters diff --git a/skore/src/skore/item/item.py b/skore/src/skore/item/item.py index d13c0ac9..9700b5fa 100644 --- a/skore/src/skore/item/item.py +++ b/skore/src/skore/item/item.py @@ -4,9 +4,9 @@ import inspect from abc import ABC, abstractmethod -from datetime import UTC, datetime +from datetime import datetime, timezone from functools import cached_property -from typing import Any +from typing import Any, Optional class Item(ABC): @@ -33,10 +33,10 @@ class Item(ABC): def __init__( self, - created_at: str | None = None, - updated_at: str | None = None, + created_at: Optional[str] = None, + updated_at: Optional[str] = None, ): - now = datetime.now(tz=UTC).isoformat() + now = datetime.now(tz=timezone.utc).isoformat() self.created_at = created_at or now self.updated_at = updated_at or now diff --git a/skore/src/skore/project.py b/skore/src/skore/project.py index d3143afa..e0701deb 100644 --- a/skore/src/skore/project.py +++ b/skore/src/skore/project.py @@ -3,7 +3,7 @@ import logging from functools import singledispatchmethod from pathlib import Path -from typing import Any, Literal +from typing import Any, Literal, Union from skore.item import ( Item, @@ -163,7 +163,7 @@ class ProjectLoadError(Exception): """Failed to load project.""" -def load(project_name: str | Path) -> 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, diff --git a/skore/src/skore/ui/app.py b/skore/src/skore/ui/app.py index 13294934..3f34b46f 100644 --- a/skore/src/skore/ui/app.py +++ b/skore/src/skore/ui/app.py @@ -8,10 +8,11 @@ from skore.project import Project, load from skore.ui.dependencies import get_static_path from skore.ui.project_routes import router as project_router +from typing import Optional def create_app( - project: Project | None = None, lifespan: Lifespan | None = None + project: Optional[Project] = None, lifespan: Optional[Lifespan] = None ) -> FastAPI: """FastAPI factory used to create the API to interact with `stores`.""" app = FastAPI(lifespan=lifespan) diff --git a/skore/tests/conftest.py b/skore/tests/conftest.py index a43043d9..79a645f4 100644 --- a/skore/tests/conftest.py +++ b/skore/tests/conftest.py @@ -1,11 +1,11 @@ -from datetime import UTC, datetime +from datetime import datetime, timezone import pytest @pytest.fixture def mock_now(): - return datetime.now(tz=UTC) + return datetime.now(tz=timezone.utc) @pytest.fixture diff --git a/skore/tests/unit/item/test_item_repository.py b/skore/tests/unit/item/test_item_repository.py index 27bdd416..427625ca 100644 --- a/skore/tests/unit/item/test_item_repository.py +++ b/skore/tests/unit/item/test_item_repository.py @@ -1,4 +1,4 @@ -from datetime import UTC, datetime +from datetime import datetime, timezone import pytest from skore.item import ItemRepository, MediaItem @@ -6,7 +6,7 @@ class TestItemRepository: def test_get_item(self): - now = datetime.now(tz=UTC).isoformat() + now = datetime.now(tz=timezone.utc).isoformat() item_representation = dict( media_bytes=b"media", media_encoding="utf-8", @@ -35,7 +35,7 @@ def test_get_item(self): repository.get_item("key2") def test_put_item(self): - now = datetime.now(tz=UTC).isoformat() + now = datetime.now(tz=timezone.utc).isoformat() item = MediaItem( media_bytes=b"media", media_encoding="utf-8", @@ -61,7 +61,7 @@ def test_put_item(self): } } - now2 = datetime.now(tz=UTC).isoformat() + now2 = datetime.now(tz=timezone.utc).isoformat() item2 = MediaItem( media_bytes=b"media2", media_encoding="utf-8",