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

Support Python 3.13 #227

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
python-version: "3.10"

- name: Install poetry
run: pip install urllib3==1.26.15 poetry==1.8.3
run: pip install urllib3==1.26.15 poetry==1.8.5

- name: Load cached venv
id: cached-poetry-dependencies
Expand All @@ -43,7 +43,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [ "3.9", "3.10", "3.11", "3.12" ]
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]

steps:
- uses: actions/checkout@v4
Expand All @@ -54,7 +54,7 @@ jobs:
python-version: ${{ matrix.python-version }}

- name: Install poetry
run: pip install urllib3==1.26.15 poetry==1.4.0
run: pip install urllib3==1.26.15 poetry==1.8.5

- name: Load cached venv
id: cached-poetry-dependencies
Expand Down
10 changes: 3 additions & 7 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,15 @@ build:
jobs:
pre_build:
- cp -r examples docs/source/
post_create_environment:
- pip install poetry
post_install:
- pip install --no-cache-dir poetry==1.8.5
- poetry export -f requirements.txt -o requirements.txt -E all --without-hashes
feldlime marked this conversation as resolved.
Show resolved Hide resolved
- pip install --no-cache-dir -r requirements.txt
- VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install -E all --no-root --with docs

sphinx:
builder: html
configuration: docs/source/conf.py
fail_on_warning: false

python:
install:
- requirements: docs/requirements.txt

formats:
- pdf
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## Unreleased

### Added

- `use_gpu` for PureSVD ([#229](https://github.com/MobileTeleSystems/RecTools/pull/229))
- `from_params` method for models and `model_from_params` function ([#252](https://github.com/MobileTeleSystems/RecTools/pull/252))

- Python 3.13 support ([#227](https://github.com/MobileTeleSystems/RecTools/pull/227))

## [0.10.0] - 16.01.2025

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ install: .venv .reports
poetry run pytest ${TESTS} --cov=${SOURCES} --cov-report=xml

.doctest:
poetry run pytest --doctest-modules ${SOURCES} --ignore=rectools/models/lightfm.py
poetry run pytest --doctest-modules ${SOURCES} --ignore=rectools/dataset/torch_datasets.py --ignore=rectools/models/dssm.py --ignore=rectools/tools/ann.py

coverage: .venv .reports
poetry run coverage run --source ${SOURCES} --module pytest
Expand Down
876 changes: 811 additions & 65 deletions poetry.lock

Large diffs are not rendered by default.

36 changes: 25 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3",
"Topic :: Software Development :: Libraries :: Python Modules",
"Intended Audience :: Science/Research",
Expand All @@ -52,13 +53,20 @@ packages = [


[tool.poetry.dependencies]
python = ">=3.9, <3.13"
python = ">=3.9, <3.14"
numpy = [
{version = ">=1.22, <2.0.0", python = "<3.12"},
{version = ">=1.26, <2.0.0", python = "3.12"} # numpy <1.26 fails to install on Python 3.12
{version = ">=1.26, <2.0.0", python = "3.12"}, # numpy <1.26 fails to install on Python 3.12
{version = ">=2.1.0, <3.0.0", python = ">=3.13"} # numpy <2.1 fails to install on Python 3.13
]
pandas = [
{version = ">=1.5.0, <3.0.0", python = "<3.13"},
{version = ">=2.2.3, <3.0.0", python = ">=3.13"} # pandas <2.2.3 fails to install on Python 3.13
]
scipy = [
{version = "^1.10.1, <1.13", python = "<3.10"}, # there is a bug in 1.13* https://github.com/scipy/scipy/issues/20670
{version = ">=1.14.1, <2.0.0", python = ">=3.10"} # scipy >=1.14.1 fails to install on Python 3.9
]
pandas = ">=1.5.0, <3.0.0"
scipy = "^1.10.1, <1.13" # in 1.13 were introduced significant changes breaking our logic
tqdm = "^4.27.0"
implicit = "^0.7.1"
attrs = ">=19.1.0,<24.0.0"
Expand All @@ -76,17 +84,16 @@ nmslib-metabrainz = {version = "^2.1.3", python = ">=3.11, <3.13", optional = tr

# The latest torch version available for MacOSX + x86_64 is 2.2.2
torch = [
{version = ">=1.6.0, <2.3.0", markers = "sys_platform == 'darwin' and platform_machine == 'x86_64'", optional = true},
{version = ">=1.6.0, <3.0.0", optional = true}
{version = ">=1.6.0, <2.3.0", python = "<3.13", markers = "sys_platform == 'darwin' and platform_machine == 'x86_64'", optional = true},
{version = ">=1.6.0, <3.0.0", python = "<3.13", optional = true},
]
pytorch-lightning = {version = ">=1.6.0, <3.0.0", optional = true}
pytorch-lightning = {version = ">=1.6.0, <3.0.0", python = "<3.13", optional = true}

ipywidgets = {version = ">=7.7,<8.2", optional = true}
plotly = {version="^5.22.0", optional = true}
nbformat = {version = ">=4.2.0", optional = true}
cupy-cuda12x = {version = "^13.3.0", python = "<3.13", optional = true}


[tool.poetry.extras]
lightfm = ["rectools-lightfm"]
nmslib = ["nmslib", "nmslib-metabrainz"]
Expand All @@ -103,13 +110,13 @@ all = [


[tool.poetry.group.dev.dependencies]
black = "24.4.2"
black = "24.10.0"
isort = "5.13.2"
pylint = "3.1.0"
mypy = "1.13.0"
flake8 = "7.0.0"
bandit = "1.7.8"
pytest = "8.1.1"
pytest = "8.3.3"
radon = "6.0.1"
coverage = "7.5.0"
autopep8 = "2.1.0"
Expand All @@ -122,10 +129,17 @@ pytest-mock = "3.14.0"
click = "8.1.7"
gitpython = "3.1.43"

[tool.poetry.group.docs]
optional = true

[tool.poetry.group.docs.dependencies]
sphinx = "5.1.1"
nbsphinx = "0.8.9"
sphinx-rtd-theme = "1.0.0"

[tool.black]
line-length = 120
target-version = ["py39", "py310", "py311", "py312"]
target-version = ["py39", "py310", "py311", "py312", "py313"]


[build-system]
Expand Down
2 changes: 1 addition & 1 deletion rectools/metrics/scoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,4 @@ def calc_metrics( # noqa # pylint: disable=too-many-branches,too-many-locals,t
if len(results) < expected_results_len:
warnings.warn("Custom metrics are not supported.")

return results
return {k: v.item() if hasattr(v, "item") else v for k, v in results.items()}
17 changes: 15 additions & 2 deletions tests/dataset/test_torch_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,28 @@
# limitations under the License.

# pylint: disable=attribute-defined-outside-init,consider-using-enumerate
import sys

import numpy as np
import pandas as pd
import pytest
import torch

try:
import torch
except ImportError:
pass

from scipy import sparse

from rectools.columns import Columns
from rectools.dataset import Dataset
from rectools.dataset.torch_datasets import DSSMItemDataset, DSSMTrainDataset, DSSMUserDataset

try:
from rectools.dataset.torch_datasets import DSSMItemDataset, DSSMTrainDataset, DSSMUserDataset
except ModuleNotFoundError:
pass

pytestmark = pytest.mark.skipif(sys.version_info >= (3, 13), reason="`torch` is not compatible with Python >= 3.13")


class WithFixtures:
Expand Down
31 changes: 27 additions & 4 deletions tests/models/test_dssm.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,48 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import sys
import typing as tp

import numpy as np
import pandas as pd
import pytest
from lightning_fabric import seed_everything

try:
from lightning_fabric import seed_everything
except ImportError:
pass

try:
import pytorch_lightning # noqa # pylint: disable=unused-import

filter_warnings_decorator = pytest.mark.filterwarnings(
"ignore::pytorch_lightning.utilities.warnings.PossibleUserWarning"
)
except ImportError:

def filter_warnings_decorator(func): # type: ignore
return func


from rectools.columns import Columns
from rectools.dataset import Dataset
from rectools.exceptions import NotFittedError
from rectools.models import DSSMModel
from rectools.models.dssm import DSSM

try:
from rectools.models import DSSMModel
from rectools.models.dssm import DSSM
except ModuleNotFoundError:
pass
from rectools.models.vector import ImplicitRanker
from tests.models.utils import assert_dumps_loads_do_not_change_model, assert_second_fit_refits_model

from .data import INTERACTIONS

pytestmark = pytest.mark.skipif(sys.version_info >= (3, 13), reason="`torch` is not compatible with Python >= 3.13")


@pytest.mark.filterwarnings("ignore::pytorch_lightning.utilities.warnings.PossibleUserWarning")
@filter_warnings_decorator
@pytest.mark.filterwarnings("ignore::UserWarning")
class TestDSSMModel:
def setup_method(self) -> None:
Expand Down
30 changes: 26 additions & 4 deletions tests/models/test_implicit_knn.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def test_with_whitelist(self, dataset: Dataset, filter_viewed: bool, expected: p
pd.DataFrame(
{
Columns.TargetItem: [11, 11, 12, 12],
Columns.Item: [12, 15, 11, 14],
Columns.Item: [12, 14, 11, 14],
Columns.Rank: [1, 2, 1, 2],
}
),
Expand All @@ -154,10 +154,32 @@ def test_with_whitelist(self, dataset: Dataset, filter_viewed: bool, expected: p
),
),
)
def test_i2i(
self, dataset: Dataset, filter_itself: bool, whitelist: tp.Optional[np.ndarray], expected: pd.DataFrame
) -> None:
def test_i2i(self, filter_itself: bool, whitelist: tp.Optional[np.ndarray], expected: pd.DataFrame) -> None:
base_model = TFIDFRecommender(K=5, num_threads=2)
# Recreate dataset to prevent same co-occurrence count between (11, 14) and (11, 15)
# which leads to different results in the test in Python 3.13
feldlime marked this conversation as resolved.
Show resolved Hide resolved
# This is because numpy.argpartition behavior was changed.
# See also: https://github.com/MobileTeleSystems/RecTools/pull/227#discussion_r1941872699
interactions = pd.DataFrame(
[
[10, 11],
[10, 12],
[10, 14],
[20, 11],
[20, 12],
[20, 13],
[30, 11],
[30, 12],
[30, 14],
[40, 11],
[40, 15],
[40, 17],
],
columns=Columns.UserItem,
)
interactions[Columns.Weight] = 1
interactions[Columns.Datetime] = "2021-09-09"
dataset = Dataset.construct(interactions)
model = ImplicitItemKNNWrapperModel(model=base_model).fit(dataset)
actual = model.recommend_to_items(
target_items=np.array([11, 12]),
Expand Down
2 changes: 1 addition & 1 deletion tests/models/test_pure_svd.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,8 @@ def test_from_config(self, mocker: MockerFixture, use_gpu: bool) -> None:
def test_get_config(
self, mocker: MockerFixture, random_state: tp.Optional[int], simple_types: bool, use_gpu: bool
) -> None:
mocker.patch("rectools.models.pure_svd.cp.cuda.is_available", return_value=True)
mocker.patch("rectools.models.pure_svd.cp", return_value=True)
mocker.patch("rectools.models.pure_svd.cp.cuda.is_available", return_value=True)
model = PureSVDModel(
factors=100,
tol=1.0,
Expand Down
2 changes: 2 additions & 0 deletions tests/models/test_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import sys
import typing as tp
from tempfile import NamedTemporaryFile
from unittest.mock import MagicMock
Expand Down Expand Up @@ -182,6 +183,7 @@ def test_fails_on_incorrect_model_cls(self, mode: tp.Literal["pydantic", "dict"]
with pytest.raises(ValidationError):
model_from_config(config)

@pytest.mark.skipif(sys.version_info >= (3, 13), reason="`torch` is not compatible with Python 3.13")
@pytest.mark.parametrize("model_cls", ("rectools.models.DSSMModel", DSSMModel))
def test_fails_on_model_cls_without_from_config_support(self, model_cls: tp.Any) -> None:
config = {"cls": model_cls}
Expand Down
10 changes: 9 additions & 1 deletion tests/tools/test_ann.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,22 @@
# limitations under the License.

import pickle
import sys
from collections.abc import Hashable
from typing import Callable, Dict, List, Union

import numpy as np
import pytest

from rectools.dataset import IdMap
from rectools.tools import ItemToItemAnnRecommender, UserToItemAnnRecommender

try:
from rectools.tools import ItemToItemAnnRecommender, UserToItemAnnRecommender
except ImportError:
pass


pytestmark = pytest.mark.skipif(sys.version_info >= (3, 13), reason="`nsmlib` is not compatible with Python >= 3.13")


class TestItemToItemAnnRecommender:
Expand Down