Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ hub/
__pycache__/
.pytest_cache/
.mypy_cache/
.ruff_cache/

# pixi environments
.pixi/*
!.pixi/config.toml
*.py[cod]
*$py.class
py36-64/
Expand Down
23 changes: 10 additions & 13 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,17 @@ repos:
exclude: skops/card/tests/examples
- id: check-case-conflict
- id: check-merge-conflict
- repo: https://github.com/psf/black
rev: 23.9.1
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.292
rev: v0.16.6
hooks:
- id: ruff
args: ["--fix", "--show-source"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.6.0
- id: ruff-check
args: ["--fix", "--output-format=full"]
- id: ruff-format
- repo: https://github.com/facebook/pyrefly-pre-commit
# Pyrefly version. The hook runs `pyrefly check` on the whole project, using
# the [tool.pyrefly] section in pyproject.toml.
rev: 1.2.0
hooks:
- id: mypy
args: [--config-file=pyproject.toml]
exclude: "spaces/"
additional_dependencies: [types-requests>=2.28.5]
- id: pyrefly-check
name: pyrefly (type checking)
7 changes: 5 additions & 2 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ And you can choose an environment to run the tests with:

pixi run -e ci-sklearn15 tests

In order to setup ``pre-commit`` hooks, you'd need to run the linter once, ignoring
the outputs:
We use `ruff <https://docs.astral.sh/ruff/>`_ for formatting and linting, and
`pyrefly <https://pyrefly.org/>`_ for type checking. Both are run through
``pre-commit``, with their versions pinned in ``.pre-commit-config.yaml``. In order
to setup the ``pre-commit`` hooks, you'd need to run the linter once, ignoring the
outputs:

.. code:: bash

Expand Down
8 changes: 4 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.. -*- mode: rst -*-

|readthedocs| |github-actions| |Codecov| |PyPI| |Black|
|readthedocs| |github-actions| |Codecov| |PyPI| |Ruff|

.. |readthedocs| image:: https://readthedocs.org/projects/skops/badge/?version=latest&style=flat
:target: https://skops.readthedocs.io/en/latest/
Expand All @@ -18,9 +18,9 @@
:target: https://pypi.org/project/skops
:alt: PyPi

.. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
:alt: Black
.. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
:target: https://github.com/astral-sh/ruff
:alt: Ruff

.. image:: https://raw.githubusercontent.com/skops-dev/skops/main/docs/images/logo.png
:width: 500
Expand Down
4 changes: 2 additions & 2 deletions examples/plot_california_housing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1010,8 +1010,8 @@
# %%

fig, ax = plt.subplots()
for weight in params["weights"]: # type: ignore
for p in params["p"]: # type: ignore
for weight in params["weights"]:
for p in params["p"]:
query = f"param_weights=='{weight}' & param_p=={p}"
df_subset = df_cv.query(query)
df_subset.plot(
Expand Down
1,455 changes: 102 additions & 1,353 deletions pixi.lock

Large diffs are not rendered by default.

33 changes: 22 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,25 @@ Repository = "http://github.com/skops-dev/skops"
Issues = "http://github.com/skops-dev/skops/issues"
Changelog = "https://skops.readthedocs.io/en/stable/changes.html"

[tool.black]
[tool.ruff]
line-length = 88
target_version = ['py310', 'py311', 'py312']
preview = true
extend-exclude = [
# locally installed pixi environments
".pixi",
# These markdown files are data (model card template and test fixtures) and
# must be kept verbatim; ruff would otherwise format the python code blocks in
# them.
"skops/card/default_template.md",
"skops/card/tests/examples",
]

[tool.ruff]
# all rules can be found here: https://beta.ruff.rs/docs/rules/
[tool.ruff.lint]
# all rules can be found here: https://docs.astral.sh/ruff/rules/
select = ["E", "F", "W", "I"]

[tool.ruff.lint.isort]
known-first-party = ["skops"]

[tool.pytest.ini_options]
doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS"
filterwarnings = [
Expand All @@ -88,10 +98,13 @@ omit = [
"skops/conftest.py",
]

[tool.mypy]
exclude = "(\\w+/)*test_\\w+\\.py$|spaces/skops_model_card_creator|old"
ignore_missing_imports = true
no_implicit_optional = true
[tool.pyrefly]
project-includes = ["skops", "examples", "scripts", "docs/conf.py"]
# Replace imports that cannot be resolved (e.g. numpy, scikit-learn, which are not
# installed in the pre-commit environment) with `Any` instead of erroring.
ignore-missing-imports = ["*"]
# The `legacy` preset is the one pyrefly recommends for projects migrating from mypy.
preset = "legacy"

[tool.pixi.workspace]
channels = ["conda-forge"]
Expand Down Expand Up @@ -124,8 +137,6 @@ fairlearn = ">=0.7.0"
[tool.pixi.feature.tests.dependencies]
pytest = ">=7"
pytest-cov = ">=2.9.0"
flake8 = ">=3.8.2"
types-requests = ">=2.28.5"
flaky = ">=3.7.0"
rich = ">=12"
matplotlib = ">=3.3"
Expand Down
2 changes: 1 addition & 1 deletion skops/card/_markup.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ def _parse_div(self, item) -> str:
kvparts.append(k)
else:
kvparts.append(f'{k}="{v}"')
tags.append(f' {" ".join(kvparts)}')
tags.append(f" {' '.join(kvparts)}")
tags.append(">")

start = "".join(tags)
Expand Down
4 changes: 2 additions & 2 deletions skops/card/_model_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def _check_table(self) -> None:
self._is_pandas_df = False

if self._is_pandas_df:
ncols = len(self.table.columns) # type: ignore
ncols = len(self.table.columns) # pyrefly: ignore[missing-attribute]
else:
ncols = len(self.table)
if ncols == 0:
Expand All @@ -218,7 +218,7 @@ def format(self) -> str:

def __repr__(self) -> str:
if self._is_pandas_df:
nrows, ncols = self.table.shape # type: ignore
nrows, ncols = self.table.shape # pyrefly: ignore[missing-attribute]
else:
# table cannot be empty, so no checks needed here
ncols = len(self.table)
Expand Down
2 changes: 1 addition & 1 deletion skops/card/tests/test_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def test_model_caching(skops_model_card, iris_skops_file, destination_path):
assert mock_load_model.call_count == 1 # cached call


CUSTOM_TEMPLATES = [None, {}, {"A Title", "Another Title", "A Title/A Section"}] # type: ignore
CUSTOM_TEMPLATES = [None, {}, {"A Title", "Another Title", "A Title/A Section"}]


class TestAddModelPlot:
Expand Down
2 changes: 1 addition & 1 deletion skops/card/tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from unittest.mock import Mock, patch

import pytest
import yaml # type: ignore
import yaml

from skops.card import parse_modelcard
from skops.card._parser import PandocParser, check_pandoc_installed
Expand Down
4 changes: 2 additions & 2 deletions skops/io/_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ def __init__(
self.trusted = self._get_trusted(trusted, default=[])
# TODO: deal with case that __id__ is unknown or prevent it from
# happening
self.cached = load_context.get_object(state.get("__id__")) # type: ignore
self.children = {} # type: ignore
self.cached = load_context.get_object(state.get("__id__")) # pyrefly: ignore[bad-argument-type]
self.children = {}

def _construct(self):
# TODO: FIXME This causes a recursion error when loading a cached
Expand Down
12 changes: 6 additions & 6 deletions skops/io/_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def dict_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]:
continue
if np.isscalar(key) and hasattr(key, "item"):
# convert numpy value to python object
key = key.item() # type: ignore
key = key.item()
content[key] = get_state(value, save_context)
res["content"] = content
res["key_types"] = key_types
Expand Down Expand Up @@ -228,7 +228,7 @@ def isnamedtuple(self, t) -> bool:
# This is needed since namedtuples need to have the args when
# initialized.
b = t.__bases__
if len(b) != 1 or b[0] != tuple:
if len(b) != 1 or b[0] is not tuple:
return False
f = getattr(t, "_fields", None)
if not isinstance(f, tuple):
Expand Down Expand Up @@ -315,7 +315,7 @@ def _construct(self):
namespace = self.children["namespace"].construct()
instance = partial(func, *args, **kwds) # always use partial, not a subclass
# partial always has __setstate__
instance.__setstate__((func, args, kwds, namespace)) # type: ignore
instance.__setstate__((func, args, kwds, namespace))
return instance


Expand Down Expand Up @@ -492,7 +492,7 @@ def _construct(self):
# bypasses the __init__, and then we set the attributes. This solves the
# issue of required init arguments. Note that the instance created here
# might not be valid until all its attributes have been set below.
instance = cls.__new__(cls) # type: ignore
instance = cls.__new__(cls)

if not self.children["attrs"]:
# nothing more to do
Expand Down Expand Up @@ -553,9 +553,9 @@ def get_unsafe_set(self) -> set[str]:
res = super().get_unsafe_set()
obj_node = self.children["obj"]
res.add(
obj_node.module_name # type: ignore
obj_node.module_name # pyrefly: ignore[missing-attribute]
+ "."
+ obj_node.class_name # type: ignore
+ obj_node.class_name # pyrefly: ignore[missing-attribute]
+ "."
+ self.children["func"]
)
Expand Down
8 changes: 3 additions & 5 deletions skops/io/_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ def __init__(
) -> None:
super().__init__(state, load_context, trusted)
self.type = state["type"]
self.trusted = self._get_trusted(
trusted, [np.ndarray] + NUMPY_DTYPE_TYPE_NAMES # type: ignore
)
self.trusted = self._get_trusted(trusted, [np.ndarray] + NUMPY_DTYPE_TYPE_NAMES)
if self.type == "numpy":
self.children = {
"content": io.BytesIO(load_context.src.read(state["file"]))
Expand Down Expand Up @@ -349,11 +347,11 @@ def _construct(self):
# a C wrapper: https://github.com/numpy/numpy/pull/23020
try:
# numpy>=2
from numpy._core._multiarray_umath import ( # type: ignore
from numpy._core._multiarray_umath import (
_ArrayFunctionDispatcher,
)
except ImportError:
from numpy.core._multiarray_umath import ( # type: ignore
from numpy.core._multiarray_umath import (
_ArrayFunctionDispatcher,
)

Expand Down
4 changes: 2 additions & 2 deletions skops/io/_persist.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ def get_untrusted_types(
if data:
content = io.BytesIO(data)
else:
# mypy doesn't understand that file cannot be None here, thus ignore
content = file # type: ignore
# the type checker doesn't understand that file cannot be None here
content = file # pyrefly: ignore[bad-assignment]

with ZipFile(content, "r") as zip_file:
schema = json.loads(zip_file.read("schema.json"))
Expand Down
1 change: 1 addition & 0 deletions skops/io/_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@
version Y instead.

"""

PROTOCOL = 2
13 changes: 7 additions & 6 deletions skops/io/_sklearn.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,10 @@ def __init__(

def loss_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]:
reduce = obj.__reduce__()
if type(obj) == reduce[0]:
if type(obj) is reduce[0]:
state = reduce_get_state(obj, save_context)
state["__loader__"] = "LossNode"
elif type(obj) == reduce[1][0]:
elif type(obj) is reduce[1][0]:
# The output is commonly of the form:
# >>> CyPinballLoss(1).__reduce__()
# (<cyfunction __pyx_unpickle_CyPinballLoss at 0x7b1d00099ff0>,
Expand Down Expand Up @@ -363,10 +363,11 @@ def _construct(self):
# TODO: remove once support for sklearn<1.2 is dropped.
# Starting from sklearn 1.2, _DictWithDeprecatedKeys is removed as it's no
# longer needed for GraphicalLassoCV, see #187.
if _DictWithDeprecatedKeys is not None:
# skops requires scikit-learn>=1.2, so this block never runs in the test matrix.
if _DictWithDeprecatedKeys is not None: # pragma: no cover
GET_STATE_DISPATCH_FUNCTIONS.append(
(_DictWithDeprecatedKeys, _DictWithDeprecatedKeys_get_state)
)
NODE_TYPE_MAPPING[
("_DictWithDeprecatedKeysNode", PROTOCOL)
] = _DictWithDeprecatedKeysNode # type: ignore
NODE_TYPE_MAPPING[("_DictWithDeprecatedKeysNode", PROTOCOL)] = (
_DictWithDeprecatedKeysNode
)
18 changes: 9 additions & 9 deletions skops/io/tests/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def _is_builtin(val):
_assert_generic_objects_equal(subval1, subval2, path=f"{path}[]")
return

assert type(val1) == type(val2), f"Path: type({path})"
assert type(val1) is type(val2), f"Path: type({path})"
if hasattr(val1, "__dict__"):
assert_params_equal(val1.__dict__, val2.__dict__, path=f"{path}.__dict__")
elif _is_builtin(val1):
Expand Down Expand Up @@ -84,7 +84,7 @@ def _assert_vals_equal(val1, val2, path=""):
# Some objects return a tuple of parameters, others a dict.
state1 = val1.__getstate__()
state2 = val2.__getstate__()
assert type(state1) == type(state2), f"Path: {path}"
assert type(state1) is type(state2), f"Path: {path}"
if isinstance(state1, tuple):
_assert_tuples_equal(state1, state2, path=path)
else:
Expand All @@ -98,12 +98,12 @@ def _assert_vals_equal(val1, val2, path=""):
# for arrays with at least 2 dimensions, check that contiguity is
# preserved, but only if the array is not a view
if val1.squeeze().ndim > 1 and val1.flags["OWNDATA"]:
assert (
val1.flags["C_CONTIGUOUS"] is val2.flags["C_CONTIGUOUS"]
), f"Path: {path}.flags"
assert (
val1.flags["F_CONTIGUOUS"] is val2.flags["F_CONTIGUOUS"]
), f"Path: {path}.flags"
assert val1.flags["C_CONTIGUOUS"] is val2.flags["C_CONTIGUOUS"], (
f"Path: {path}.flags"
)
assert val1.flags["F_CONTIGUOUS"] is val2.flags["F_CONTIGUOUS"], (
f"Path: {path}.flags"
)
if val1.dtype == object:
assert val2.dtype == object, f"Path: {path}.dtype"
assert val1.shape == val2.shape, f"Path: {path}.shape"
Expand Down Expand Up @@ -166,7 +166,7 @@ def assert_params_equal(params1, params2, path=""):
warnings.filterwarnings("ignore", category=FutureWarning, module="sklearn")
val1, val2 = params1[key], params2[key]
subpath = f"{path}[{key}]"
assert type(val1) == type(val2), f"Path: type({subpath})"
assert type(val1) is type(val2), f"Path: type({subpath})"

if _is_steps_like(val1):
# Deal with Pipeline.steps, FeatureUnion.transformer_list, etc.
Expand Down
Loading