Source code for repobee.ext.defaults

"""The defaults plugin contains all default hook implementations.

The goal is to make core parts of repobee pluggable using hooks that only
return the first result that is not None. The standard behavior will be
provided by the default plugin (this one), which implements all of the required
hooks.  The default plugin will always be run last, so any user-defined hooks
will run before it and therefore effectively override the default hooks.

Currently, only the peer review related generate_review_allocations hook has a
default implementation.

.. module:: defaults
    :synopsis: Plugin that provides the default behavior for core repobee
        functionality.

.. moduleauthor:: Simon Larsén
"""
import random
from typing import Callable, Iterable, Mapping, List

import daiquiri

from repobee_plug import repobee_hook

LOGGER = daiquiri.getLogger(name=__file__)


[docs]@repobee_hook def generate_review_allocations( master_repo_name: str, students: Iterable[str], num_reviews: int, review_team_name_function: Callable[[str, str], str], ) -> Mapping[str, List[str]]: """Generate a (peer_review_team -> reviewers) mapping for each student repository (i.e. <student>-<master_repo_name>), where len(reviewers) = num_reviews. review_team_name_function should be used to generate review team names. It should be called like: .. code-block:: python review_team_name_function(master_repo_name, student) .. important:: There must be strictly more students than reviewers per repo (`num_reviews`). Otherwise, allocation is impossible. Args: master_repo_name: Name of a master repository. students: Students for which to generate peer review allocations. num_reviews: Amount of reviews each student should perform (and consequently amount of reviewers per repo) review_team_name_function: A function that takes a master repo name as its first argument, and a student username as its second, and returns a review team name. Returns: a (peer_review_team -> reviewers) mapping for each student repository. """ students = list(students) if num_reviews >= len(students): raise ValueError("num_reviews must be less than len(students)") if num_reviews <= 0: raise ValueError("num_reviews must be greater than 0") if len(students) < 2: raise ValueError( "there must be at least 2 students for peer review, " "but {} were provided".format(len(students)) ) random.shuffle(students) # create a list of lists, where each non-first list is a left-shifted # version of the previous list (lists wrap around) # e.g. for students [4, 3, 1] and num_reviews = 2, the result is # allocations = [[4, 3, 1], [3, 1, 4], [1, 4, 3]] and means that # student 4 gets reviewed by 3 and 1, 3 by 1 and 4 etc. allocations = [students] for _ in range(num_reviews): next_reviewers = list(allocations[-1]) next_reviewers.append(next_reviewers.pop(0)) # shift list left allocations.append(next_reviewers) review_allocations = { review_team_name_function(reviewee, master_repo_name): reviewers for reviewee, *reviewers in zip(*allocations) } return review_allocations