Contributing#

scikit-lr is developed on GitHub, where bugs and feature requests are tracked as issues and changes are submitted as pull requests. Communications on all channels should respect our code of conduct.

scikit-lr follows the conventions of scikit-learn, so this guide only covers what is specific to scikit-lr and links to the scikit-learn contributing guide for the rest.

Ways to contribute#

Code is not the only way to help the project. Reporting the problems that you find when using scikit-lr, improving the documentation, investigating the open issues and reviewing the pull requests of other contributors are just as valuable. It also helps to reference scikit-lr from your articles and websites, or to star it on GitHub to say that you use it.

Licensing your contribution#

Pull requests are accepted under the MIT license of the project, so you must have the right to license every part of your contribution under it. Code taken from other projects must have a compatible license and keep its notice.

Automated contributions policy#

Contributing to scikit-lr requires human judgment and an understanding of the project, so issues and pull requests generated by fully automated tools are closed without review.

You may use AI tools to help you, but you must review, understand and test every change that they make before submitting it under your name, and be able to explain it when asked. State in the description of the pull request what you used them for, as its template asks. Do not paste text generated by AI tools in issues, pull requests or comments, beyond fixing the grammar or translating your own words.

This policy does not cover the automated tools that the maintainers set up for the project, such as the bot that updates the pinned versions of the tools and the actions, or the workflows that open an issue when they fail. Their pull requests are reviewed by a maintainer before merging, like any other.

Submitting a bug report or a feature request#

Before opening an issue, check that it is not already covered by another issue or pull request. A feature request should explain the problem that it solves and, for a new estimator or metric, cite the article that describes it.

A good bug report contains a minimal reproducer: a short snippet that anyone can run to see the problem, with synthetic data instead of your own. Include the full traceback of any exception, and the versions of scikit-lr, of its dependencies and of the platform, which sklr.show_versions() prints:

$ python -c "import sklr; sklr.show_versions()"

If sklr cannot be imported, report the error, and the output of sklearn.show_versions() instead.

Contributing code and documentation#

The changes are submitted as pull requests from a fork of the repository. To avoid duplicated work, search the issues and pull requests first and, for anything beyond a small fix, open an issue to discuss it before writing the code.

Development workflow#

  1. Set up the development environment, from a clone of your fork with the main repository as the upstream remote.

  2. Create a branch for your changes from the latest master:

    $ git fetch upstream
    $ git switch -c my-feature upstream/master
    
  3. Commit your changes. The pre-commit hooks run on each commit and may fix some files themselves; in that case, add them again and repeat the commit:

    $ git add <files>
    $ git commit
    
  4. Push the branch to your fork and open a pull request against master:

    $ git push -u origin my-feature
    

To bring in the changes made to master meanwhile, merge upstream/master into your branch and solve the conflicts, if any.

Pull request checklist#

Before asking for a review, make sure that:

  • The pull request is focused on one change and does not modify unrelated lines.

  • The title follows Conventional Commits (feat:, fix:, docs:, test:, …) and summarizes the change. The description explains why it is needed and links the issue that it solves with a keyword such as Fixes #<issue>, so that the issue is closed with the merge.

  • The code follows the coding guidelines.

  • New code is tested, and a bug fix comes with a test that fails without it (see Testing and improving test coverage).

  • The public API is documented, and new features are described in the user guide and shown in an example (see Documentation).

  • A change that affects users has a changelog entry.

  • The checks pass locally, and the CI passes once the pull request is opened.

An incomplete contribution can be opened as a draft pull request, and marked as ready for review when it is finished.

Continuous integration#

The pull requests are checked by these workflows of GitHub Actions:

  • “Unit tests” builds scikit-lr and runs its tests on Linux, macOS and Windows, with every supported Python version and with the minimum and latest versions of the dependencies.

  • “Code quality checks” runs the pre-commit hooks on the whole repository.

  • “Wheels and source distribution” builds and tests the wheels of every platform and the source distribution, as they are released.

  • “Coverage” measures the lines that the tests cover and reports the changes to Codecov.

  • “Documentation” builds the documentation, which is available as an artifact of the run.

  • “CodeQL” looks for security problems in the Python code and the workflows.

  • “Check changelog” checks that a pull request that changes the tests adds a changelog entry named after its number, unless a maintainer labels it “no changelog needed” because it does not affect users.

The workflow “Upcoming scikit-learn” runs every night instead, and builds and tests scikit-lr against the next release of scikit-learn (see Coupling with the version of scikit-learn). When it fails, it opens an issue, which it closes once it passes again.

Checks#

Before submitting a pull request, make sure that the tests and the linters pass:

$ pytest
$ pre-commit run --all-files

The pre-commit hooks run ruff (lint and format), pyrefly (type checking), cython-lint, sphinx-lint (reStructuredText) and codespell, among others. They only look at the files tracked by git, so add the new files before running them.

Testing and improving test coverage#

The tests use pytest and live in the tests directory of each subpackage. The whole suite also runs the examples of the docstrings and of the documentation pages, but you can run only the tests related to your changes:

$ pytest sklr/<subpackage>/tests/test_<module>.py
$ pytest sklr/<subpackage>/<module>.py
$ pytest doc/<page>.rst

The scikit-learn pytest tips apply here too. Every warning is turned into an error, and a test marked as an expected failure fails if it passes.

The tests of scikit-lr follow these rules:

  • Tests import the public API from its public module, as users do.

  • Every estimator passes the scikit-learn estimator checks with the parametrize_with_checks of sklr/utils/_estimator_checks.py. It runs the checks of parametrize_with_checks(), whose targets are meant for classifiers and regressors, with the targets of the rankers turned into rankings. A check that cannot pass is listed in expected_failed_checks with the reason.

  • Numerical results are checked against independent oracles: a naive implementation, a known property, or an equivalent function of NumPy, SciPy or scikit-learn, never against the output of a previous version.

  • The tests also cover the edge cases: empty and single-sample inputs, ties, different dtypes, n_jobs and reproducibility with random_state.

  • Random seeds are fixed, and tolerances are explicit, with numpy.testing.assert_allclose(), and justified in a comment.

New code should be covered by the tests. To see the lines that they miss:

$ coverage run -m pytest
$ coverage report --show-missing

The coverage of the Cython extensions is measured every night by the continuous integration, which compiles them with the linetrace option of meson.options.

Documentation#

The documentation has four parts, written in reStructuredText:

  • The docstrings of the public API, in the code of sklr, from which the API reference is generated.

  • The user guide, which explains the problems and the algorithms with the details that the API reference leaves out, in doc.

  • The examples, in examples, which Sphinx-Gallery runs and renders when the documentation is built.

  • The release history, with a file per minor release in doc/whats_new, which is generated from the changelog entries of the pull requests.

Docstrings follow the numpydoc style guide and, for what it leaves open, the guidelines for docstrings, the user guide and the references of scikit-learn. Lines are at most 88 characters long, in docstrings and in the .rst pages. The docstrings of the public API pass the validation checks of numpydoc, except the ones that scikit-learn ignores, as sklr/tests/test_docstrings.py tests. The examples of the docstrings and of the pages run as tests without the names of the module where they are, so they must import everything they use.

Changelog entries#

A pull request that affects users describes its changes in a changelog fragment, a short reStructuredText file in doc/whats_new/upcoming_changes that towncrier adds to the release history of the upcoming version when it is released. The release history is not edited by hand, so that the pull requests do not conflict with each other.

The fragment goes in the directory of the public module that the pull request changes, such as sklr, or in custom-top-level if it changes none in particular. Each directory is a section of [tool.towncrier] in pyproject.toml, so a pull request that adds a public module also adds its section there. The fragment is named <pull request>.<type>.rst, with the number of the pull request and one of these types, whose meaning the legend of the release history explains:

  • major-feature

  • feature

  • efficiency

  • enhancement

  • fix

  • api

  • other, without a badge, mostly for custom-top-level.

A pull request with more than one change of the same type and directory adds a counter to the name of the other fragments, as in <pull request>.<type>.1.rst. Each fragment is a single bullet point, which ends by crediting its authors with the :user: role and their GitHub username, and the link to the pull request is added when the release history is generated. For example, the pull request that added show_versions() added 99.feature.rst to the sklr directory:

* :func:`show_versions` prints the versions of scikit-lr, of the scikit-learn that
  it was built against and of the dependencies, to include them in bug reports.
  By :user:`Juan Carlos Alfaro Jiménez <alfaro96>`.

The documentation of the development version renders the fragments into the release history of the upcoming version. To preview it locally, run:

$ towncrier build --draft

Building the documentation#

The documentation is built with Sphinx, from the doc directory:

$ make -C doc html

The pages are written to doc/_build/html. Every warning is an error, so that the pages that would render wrongly are not published. The build needs network access, to download the inventories that link to the documentation of Python, NumPy, SciPy and scikit-learn. make -C doc clean removes the build and the pages generated from the docstrings and the examples.

Coding guidelines#

scikit-lr follows the coding guidelines of scikit-learn, and its estimators follow the scikit-learn API. In addition:

  • Only the public API is exported; every other module, function and attribute starts with an underscore. Changes to the public API are recorded in the release history.

  • The signatures of the public API have type hints, which are distributed with the package. The private code does not, as in scikit-learn.

  • The private API of scikit-learn is only imported by sklr/utils/_sklearn_compat.py, which adapts it to the supported versions, and a pre-commit hook enforces it. The internals of scikit-learn used from Cython follow their own rules.

  • Error messages include the value that was received, and warnings use a specific category.

  • Comments explain why the code does what it does, not what it does.

  • New dependencies need to be discussed in an issue first.

Code review#

Anybody can review a pull request, and doing so is a good way to learn the code base. The scikit-learn code review guidelines describe what to look at, and how to communicate it.