Skip to content

Commit

Permalink
Merge pull request #581 from mdekstrand/feature/stability-docs
Browse files Browse the repository at this point in the history
Add stability declarations to documentation
  • Loading branch information
mdekstrand authored Dec 31, 2024
2 parents 8afcca0 + 969a0a8 commit 4725505
Show file tree
Hide file tree
Showing 76 changed files with 603 additions and 149 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ jobs:
python -m lenskit.data.fetch ml-100k ml-1m ml-10m ml-20m
- name: "📕 Validate documentation examples"
run: |
pytest --cov=lenskit/lenskit --cov=lenskit-funksvd/lenskit --cov=lenskit-implicit/lenskit --cov=lenskit-hpf/lenskit --nbval-lax --doctest-glob='*.rst' --log-file test-docs.log docs */lenskit
pytest --cov=lenskit/lenskit --cov=lenskit-funksvd/lenskit --cov=lenskit-implicit/lenskit --cov=lenskit-hpf/lenskit --nbval-lax --doctest-glob='*.rst' --ignore='docs/_ext' --log-file test-docs.log docs */lenskit
- name: "📐 Coverage results"
if: '${{ !cancelled() }}'
run: |
Expand Down
106 changes: 106 additions & 0 deletions docs/_ext/lk_stability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from __future__ import annotations

from typing import Sequence

from docutils import nodes
from docutils.parsers.rst.directives.admonitions import Admonition
from sphinx.addnodes import pending_xref
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata

STABILITY_LEVELS = {
"full": """
This API is at the **full** stability level; breaking changes for both callers
and implementers will be reserved for annual major version bumps. See
:ref:`stability-levels` for details.
""",
"caller": """
This API is at the **caller** stability level: breaking changes for code calling
this function or class will be reserved for annual major version bumps, but minor
versions may introduce changes that break subclasses or reimplementations. See
:ref:`stability-levels` for details.
""",
"testing": """
This API is at the **testing**: we will avoid gratuituous breaking changes for
callers, but may make such changes in minor versions with clear statements in the
release notes. No stability guarantees are made for subclasses or re-implementers.
See :ref:`stability-levels` for details.
""",
"internal": """
This API is at the **internal** or **experimental** stability level: it may
change at any time, and breaking changes will not necessarily be described in
the release notes. See :ref:`stability-levels` for details.
""",
}


class StabilityDirective(Admonition):
"""
A directive to note an API's stability.
"""

required_arguments = 1

def run(self) -> Sequence[nodes.Node]:
level = self.arguments[0]
text = STABILITY_LEVELS.get(level, "")
self.arguments = ["Stability: " + level.capitalize()]
self.options["class"] = ["note"]
self.content.insert(0, text, "unknown")
return super().run()


def scan_stability_notes(app, domain, objtype, contentnode):
for node in contentnode.findall(_is_stability_field):
vals = node.traverse(nodes.field_body)
if not vals:
continue

body: nodes.field_body = vals[0]
text = body.astext()
if text.lower() in STABILITY_LEVELS:
# we found a stability level — add reference
body.clear()
body.extend(
[
nodes.Text(text.capitalize()),
nodes.Text(" (see "),
nodes.inline(
"",
"",
pending_xref(
"",
nodes.inline(
"",
"stability-levels",
classes=["xref", "std", "std-ref"],
),
refdoc="api/operations",
refdomain="std",
reftype="ref",
reftarget="stability-levels",
refexplicit=False,
refwarn=True,
),
),
nodes.Text(")."),
]
)


def _is_stability_field(node):
if isinstance(node, nodes.field):
names = node.traverse(nodes.field_name)
if names:
return names[0].astext() == "Stability"


def setup(app: Sphinx) -> ExtensionMetadata:
app.add_directive("stability", StabilityDirective)
app.connect("object-description-transform", scan_stability_notes)

return {
"version": "2025.1",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
20 changes: 0 additions & 20 deletions docs/addons/hpf.rst

This file was deleted.

23 changes: 0 additions & 23 deletions docs/addons/implicit.rst

This file was deleted.

12 changes: 0 additions & 12 deletions docs/addons/index.rst

This file was deleted.

8 changes: 5 additions & 3 deletions docs/api/data-types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ Basic Data Types
Entity Identifiers
~~~~~~~~~~~~~~~~~~

.. autoclass:: EntityId

.. autoclass:: NPEntityId
.. autodata:: ID
.. autodata:: CoreID
.. autodata:: NPID

.. autodata:: IDArray
.. autodata:: IDSequence

Containers
~~~~~~~~~~
Expand Down
1 change: 1 addition & 0 deletions docs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ and may be useful in building new models and components for LensKit.
:caption: Implementation Helpers
:recursive:

lenskit.stats
lenskit.logging
lenskit.math
lenskit.parallel
Expand Down
2 changes: 2 additions & 0 deletions docs/api/operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ can directly import them:
from lenskit import recommend, score
.. stability:: full

Recommending
~~~~~~~~~~~~

Expand Down
12 changes: 9 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
# Licensed under the MIT license, see LICENSE.md for details.
# SPDX-License-Identifier: MIT

import os
import sys
from importlib.metadata import version
from pathlib import Path

from packaging.version import Version

sys.path.insert(0, os.path.abspath(".."))
sys.path.append(str((Path(__file__).parent / "_ext").resolve()))

project = "LensKit"
copyright = "2018–2024 Drexel University, Boise State University, and collaborators"
Expand All @@ -33,12 +33,14 @@
"sphinxext.opengraph",
"sphinxcontrib.bibtex",
"sphinxcontrib.mermaid",
"lk_stability",
]

# set up our filenames
# source_suffix = {".rst": "restructuredtext"}
exclude_patterns = [
"_build",
"_ext",
"Thumbs.db",
".DS_Store",
"old/*",
Expand Down Expand Up @@ -103,19 +105,23 @@
"SeedLike": "lenskit.types.SeedLike",
"RNGLike": "lenskit.types.RNGLike",
"RNGInput": "lenskit.types.RNGInput",
"IDSequence": "lenskit.data.types.IDSequence",
}
# autosummary_generate_overwrite = False
autosummary_imported_members = False
autosummary_ignore_module_all = True

# customize doc parsing
napoleon_custom_sections = [("Stability", "returns_style")]

nitpicky = True
todo_include_todos = True

# Cross-linking and external references
intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
"pandas": ("http://pandas.pydata.org/pandas-docs/stable/", None),
"pyarrow": ("https://arrow.apache.org/docs/python/", None),
"pyarrow": ("https://arrow.apache.org/docs/", None),
"numpy": ("https://numpy.org/doc/stable/", None),
"scipy": ("https://docs.scipy.org/doc/scipy/", None),
"sklearn": ("https://scikit-learn.org/stable/", None),
Expand Down
1 change: 1 addition & 0 deletions docs/guide/scorers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Classical Collaborative Filters
lenskit.als.ImplicitMFScorer
lenskit.sklearn.svd.BiasedSVDScorer
lenskit.funksvd.FunkSVDScorer
lenskit.hpf.HPFScorer


Utility Scorers
Expand Down
57 changes: 57 additions & 0 deletions docs/releases/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,63 @@ fixes for the current major version. This will typically happen early in the
year (except for important bug fixes that need to be backported to previous
stable versions).

.. _stability-levels:

Stability Levels
----------------

SemCalVer guarantees are made in accordance with an API's *stability level*, to
clearly document what users can expect and to give sufficient flexibility to
evolve experimental or in-progress APIs.

Stability levels are defined with respect to two different types of interaction
with an API:

- The *caller* that calls the function, instantiates the class, etc.
- An *implementer* that implements an interface, subclasses a class, etc.;
this is mostly relevant for classes and sometimes methods, not individual
functions.

.. important::

Stability guarantees do not take effect until :ref:`2025.1` is released.

There are four stability levels for LensKit classes, functions, etc.:

.. glossary::

Full stability
For code at the full stability level, we will avoid breaking changes for
both callers and (where applicable) implementers until the next annual major
version bump. Any methods added to a full-stability base class in a minor
version will have implementations that work in terms of the
previously-defined abstract methods. Full stability does **not** guarantee
that minor versions will not add new methods that may conflict with methods
added by implementors, so exercise care when adding public (non-underscore)
methods to subclasses.

Caller stability
For code at the caller stability level, we will avoid breaking changes for
callers until the next major version bump, but may change the code in ways
that break subclasses or interface re-implementations.

Testing stability
Testing-level interfaces are generally stable but may have breaking changes
in minor releases. Such changes will be clearly documented in the release
notes. This stability level is for code we want people to be able to start
using freely, but may need to change as we gain experience with the interface.

Internal stability
Experimental stability
Internal and experimental interfaces may change at any time, and breaking
changes will not necessarily be highlighted as such in the release notes.

.. note::

If a class or function does not specify a stability level, assume the
internal/experimental level.


.. _dep-policy:

Dependency Versioning
Expand Down
3 changes: 3 additions & 0 deletions lenskit-funksvd/lenskit/funksvd.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ class FunkSVDScorer(Component, Trainable):
This scorer is kept around for historical comparability, but ALS
:class:`~lenskit.als.BiasedMF` is usually a better option.
Stability:
Caller
Args:
features:
the number of features to train
Expand Down
5 changes: 4 additions & 1 deletion lenskit-hpf/lenskit/hpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ class HPFScorer(Component, Trainable):
.. todo::
Right now, this uses the 'rating' as a count. Actually use counts.
Stability:
Caller
Args:
features:
the number of features
kwargs:
additional arguments to pass to :py:class:`hpfrec.HPF`.
additional arguments to pass to :class:`hpfrec.HPF`.
"""

features: int
Expand Down
9 changes: 9 additions & 0 deletions lenskit-implicit/lenskit/implicit.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class BaseRec(Component, Trainable):
"""
Base class for Implicit-backed recommenders.
Stability:
Caller
Args:
delegate:
The delegate algorithm.
Expand Down Expand Up @@ -132,6 +135,9 @@ def __str__(self):
class ALS(BaseRec):
"""
LensKit interface to :py:mod:`implicit.cpu.als` (or GPU version).
Stability:
Caller
"""

def __init__(self, *args, weight=40.0, **kwargs):
Expand All @@ -148,6 +154,9 @@ def __init__(self, *args, weight=40.0, **kwargs):
class BPR(BaseRec):
"""
LensKit interface to :py:mod:`implicit.cpu.bpr` (or GPU version).
Stability:
Caller
"""

def __init__(self, *args, **kwargs):
Expand Down
Loading

0 comments on commit 4725505

Please sign in to comment.