.. _contributing: 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 :ref:`scikit-learn contributing guide ` for the rest. .. _ways_to_contribute: 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_contribution: 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: 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_bug_feature: 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 :ref:`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 :func:`sklr.show_versions` prints: .. code-block:: console $ python -c "import sklr; sklr.show_versions()" If :mod:`sklr` cannot be imported, report the error, and the output of :func:`sklearn.show_versions` instead. .. _contributing_code: 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: Development workflow ~~~~~~~~~~~~~~~~~~~~ #. :ref:`Set up the development environment `, from a clone of your fork with the main repository as the ``upstream`` remote. #. Create a branch for your changes from the latest ``master``: .. code-block:: console $ git fetch upstream $ git switch -c my-feature upstream/master #. 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: .. code-block:: console $ git add $ git commit #. Push the branch to your fork and open a pull request against ``master``: .. code-block:: console $ 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. .. _pr_checklist: 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 #``, so that the issue is closed with the merge. * The code follows the :ref:`coding guidelines `. * New code is tested, and a bug fix comes with a test that fails without it (see :ref:`testing`). * The public API is documented, and new features are described in the user guide and shown in an example (see :ref:`documentation`). * A change that affects users has a :ref:`changelog entry `. * The :ref:`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: 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 :ref:`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 :ref:`sklearn_version_coupling`). When it fails, it opens an issue, which it closes once it passes again. .. _checks: Checks ------ Before submitting a pull request, make sure that the tests and the linters pass: .. code-block:: console $ 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: 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: .. code-block:: console $ pytest sklr//tests/test_.py $ pytest sklr//.py $ pytest doc/.rst The scikit-learn :ref:`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 :func:`~sklearn.utils.estimator_checks.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 :func:`numpy.testing.assert_allclose`, and justified in a comment. New code should be covered by the tests. To see the lines that they miss: .. code-block:: console $ 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: 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 :ref:`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 :ref:`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: 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 ``..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 ``..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 :func:`~sklr.show_versions` added ``99.feature.rst`` to the ``sklr`` directory: .. code-block:: rst * :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 `. The documentation of the development version renders the fragments into the release history of the upcoming version. To preview it locally, run: .. code-block:: console $ towncrier build --draft .. _building_documentation: Building the documentation ~~~~~~~~~~~~~~~~~~~~~~~~~~ The documentation is built with Sphinx, from the ``doc`` directory: .. code-block:: console $ 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: Coding guidelines ----------------- scikit-lr follows the :ref:`coding guidelines ` of scikit-learn, and its estimators follow the :ref:`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 :ref:`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: Code review ----------- Anybody can review a pull request, and doing so is a good way to learn the code base. The scikit-learn :ref:`code review guidelines ` describe what to look at, and how to communicate it.