Skip to content

marimo-learn

Utilities for use in marimo notebooks.

ConceptMapWidget

Bases: AnyWidget

A concept mapping widget where students draw labeled directed edges between concepts.

Students select a relationship term then click two concept nodes to connect them. Concept nodes can be dragged to rearrange the layout.

Attributes:

Name Type Description
question str

The question or prompt shown above the map

concepts list

List of concept names (nodes)

terms list

List of relationship terms that can label edges

correct_edges list

List of dicts with 'from', 'to', 'label' keys

value dict

State with 'edges', 'score', 'total', and 'correct' keys

Source code in src/marimo_learn/concept_map.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class ConceptMapWidget(anywidget.AnyWidget):
    """
    A concept mapping widget where students draw labeled directed edges between concepts.

    Students select a relationship term then click two concept nodes to connect them.
    Concept nodes can be dragged to rearrange the layout.

    Attributes:
        question (str): The question or prompt shown above the map
        concepts (list): List of concept names (nodes)
        terms (list): List of relationship terms that can label edges
        correct_edges (list): List of dicts with 'from', 'to', 'label' keys
        value (dict): State with 'edges', 'score', 'total', and 'correct' keys
    """

    _esm = Path(__file__).parent / "static" / "concept-map.js"

    question = traitlets.Unicode("").tag(sync=True)
    concepts = traitlets.List(trait=traitlets.Unicode()).tag(sync=True)
    terms = traitlets.List(trait=traitlets.Unicode()).tag(sync=True)
    correct_edges = traitlets.List().tag(sync=True)
    lang = traitlets.Unicode("en").tag(sync=True)
    value = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True)

    def __init__(self, question: str, concepts: list[str], terms: list[str],
                 correct_edges: list[dict] | None = None, lang: str = "en", **kwargs):
        super().__init__(**kwargs)
        self.question = question
        self.concepts = concepts
        self.terms = terms
        self.correct_edges = correct_edges if correct_edges is not None else []
        self.lang = lang

FlashcardWidget

Bases: AnyWidget

A flashcard widget with self-reported spaced repetition.

Students flip cards to reveal the answer, then rate themselves (Got it / Almost / No). Cards rated "Almost" or "No" are re-inserted into the queue; the deck is complete when all cards are rated "Got it".

Attributes:

Name Type Description
question str

Optional heading shown above the deck

cards list

List of dicts with 'front' and 'back' keys

shuffle bool

Whether to shuffle the deck initially

value dict

State with 'results' (per-card ratings/attempts) and 'complete'

Source code in src/marimo_learn/flashcard.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class FlashcardWidget(anywidget.AnyWidget):
    """
    A flashcard widget with self-reported spaced repetition.

    Students flip cards to reveal the answer, then rate themselves
    (Got it / Almost / No). Cards rated "Almost" or "No" are re-inserted
    into the queue; the deck is complete when all cards are rated "Got it".

    Attributes:
        question (str): Optional heading shown above the deck
        cards (list): List of dicts with 'front' and 'back' keys
        shuffle (bool): Whether to shuffle the deck initially
        value (dict): State with 'results' (per-card ratings/attempts) and 'complete'
    """

    _esm = Path(__file__).parent / "static" / "flashcard.js"

    question = traitlets.Unicode("").tag(sync=True)
    cards = traitlets.List().tag(sync=True)
    shuffle = traitlets.Bool(True).tag(sync=True)
    lang = traitlets.Unicode("en").tag(sync=True)
    value = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True)

    def __init__(self, cards, question="", shuffle=True, lang="en", **kwargs):
        super().__init__(**kwargs)
        self.question = question
        self.cards = cards
        self.shuffle = shuffle
        self.lang = lang

LabelingWidget

Bases: AnyWidget

A text labeling widget where students drag numbered labels to text lines.

Attributes:

Name Type Description
question str

The question text to display

labels list

List of label texts (shown on left)

text_lines list

List of text lines to be labeled (shown on right)

correct_labels dict

Mapping of line indices to lists of correct label indices

value dict

Current state with 'placed_labels', 'score', 'total', and 'correct' keys

Source code in src/marimo_learn/labeling.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class LabelingWidget(anywidget.AnyWidget):
    """
    A text labeling widget where students drag numbered labels to text lines.

    Attributes:
        question (str): The question text to display
        labels (list): List of label texts (shown on left)
        text_lines (list): List of text lines to be labeled (shown on right)
        correct_labels (dict): Mapping of line indices to lists of correct label indices
        value (dict): Current state with 'placed_labels', 'score', 'total', and 'correct' keys
    """

    # Load JavaScript from external file
    _esm = Path(__file__).parent / "static" / "labeling.js"

    # Traitlets
    question = traitlets.Unicode("").tag(sync=True)
    labels = traitlets.List(trait=traitlets.Unicode()).tag(sync=True)
    text_lines = traitlets.List(trait=traitlets.Unicode()).tag(sync=True)
    correct_labels = traitlets.Dict().tag(sync=True)
    lang = traitlets.Unicode("en").tag(sync=True)
    value = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True)

    def __init__(self, question: str, labels: list[str], text_lines: list[str], correct_labels: dict, lang: str = "en", **kwargs):
        """
        Initialize a labeling widget.

        Args:
            question: The question text
            labels: List of label texts (e.g., ["Variable declaration", "Function call", "Loop"])
            text_lines: List of text lines to be labeled (e.g., code lines, sentences)
            correct_labels: Dict mapping line index to list of correct label indices
                           Example: {0: [0, 1], 2: [2]} means line 0 should have labels 0 and 1,
                           line 2 should have label 2
        """
        super().__init__(**kwargs)
        self.question = question
        self.labels = labels
        self.text_lines = text_lines
        self.correct_labels = correct_labels
        self.lang = lang

__init__(question, labels, text_lines, correct_labels, lang='en', **kwargs)

Initialize a labeling widget.

Parameters:

Name Type Description Default
question str

The question text

required
labels list[str]

List of label texts (e.g., ["Variable declaration", "Function call", "Loop"])

required
text_lines list[str]

List of text lines to be labeled (e.g., code lines, sentences)

required
correct_labels dict

Dict mapping line index to list of correct label indices Example: {0: [0, 1], 2: [2]} means line 0 should have labels 0 and 1, line 2 should have label 2

required
Source code in src/marimo_learn/labeling.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def __init__(self, question: str, labels: list[str], text_lines: list[str], correct_labels: dict, lang: str = "en", **kwargs):
    """
    Initialize a labeling widget.

    Args:
        question: The question text
        labels: List of label texts (e.g., ["Variable declaration", "Function call", "Loop"])
        text_lines: List of text lines to be labeled (e.g., code lines, sentences)
        correct_labels: Dict mapping line index to list of correct label indices
                       Example: {0: [0, 1], 2: [2]} means line 0 should have labels 0 and 1,
                       line 2 should have label 2
    """
    super().__init__(**kwargs)
    self.question = question
    self.labels = labels
    self.text_lines = text_lines
    self.correct_labels = correct_labels
    self.lang = lang

MatchingWidget

Bases: AnyWidget

A matching question widget where students pair items from two columns using drag-and-drop.

Attributes:

Name Type Description
question str

The question text to display

left list

Items in the left column

right list

Items in the right column

correct_matches dict

Mapping of left column indices to right column indices

value dict

Current state with 'matches', 'correct', and 'score' keys

Source code in src/marimo_learn/matching.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class MatchingWidget(anywidget.AnyWidget):
    """
    A matching question widget where students pair items from two columns using drag-and-drop.

    Attributes:
        question (str): The question text to display
        left (list): Items in the left column
        right (list): Items in the right column
        correct_matches (dict): Mapping of left column indices to right column indices
        value (dict): Current state with 'matches', 'correct', and 'score' keys
    """

    # Load JavaScript from external file
    _esm = Path(__file__).parent / "static" / "matching.js"

    # Traitlets
    question = traitlets.Unicode("").tag(sync=True)
    left = traitlets.List(trait=traitlets.Unicode()).tag(sync=True)
    right = traitlets.List(trait=traitlets.Unicode()).tag(sync=True)
    correct_matches = traitlets.Dict().tag(sync=True)
    lang = traitlets.Unicode("en").tag(sync=True)
    value = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True)

    def __init__(self, question: str, left: list[str], right: list[str], correct_matches: dict, lang: str = "en", **kwargs):
        """
        Initialize a matching widget.

        Args:
            question: The question text
            left: Items in the left column
            right: Items in the right column
            correct_matches: Dict mapping left indices to right indices (e.g., {0: 2, 1: 0, 2: 1})
        """
        super().__init__(**kwargs)
        self.question = question
        self.left = left
        self.right = right
        self.correct_matches = correct_matches
        self.lang = lang

__init__(question, left, right, correct_matches, lang='en', **kwargs)

Initialize a matching widget.

Parameters:

Name Type Description Default
question str

The question text

required
left list[str]

Items in the left column

required
right list[str]

Items in the right column

required
correct_matches dict

Dict mapping left indices to right indices (e.g., {0: 2, 1: 0, 2: 1})

required
Source code in src/marimo_learn/matching.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def __init__(self, question: str, left: list[str], right: list[str], correct_matches: dict, lang: str = "en", **kwargs):
    """
    Initialize a matching widget.

    Args:
        question: The question text
        left: Items in the left column
        right: Items in the right column
        correct_matches: Dict mapping left indices to right indices (e.g., {0: 2, 1: 0, 2: 1})
    """
    super().__init__(**kwargs)
    self.question = question
    self.left = left
    self.right = right
    self.correct_matches = correct_matches
    self.lang = lang

MultipleChoiceWidget

Bases: AnyWidget

A multiple choice question widget.

Attributes:

Name Type Description
question str

The question text to display

options list

List of answer options

correct_answer int

Index of the correct answer (0-based)

explanation str

Optional explanation text shown after answering

value dict

Current state with 'selected', 'correct', and 'answered' keys

Source code in src/marimo_learn/multiple_choice.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class MultipleChoiceWidget(anywidget.AnyWidget):
    """
    A multiple choice question widget.

    Attributes:
        question (str): The question text to display
        options (list): List of answer options
        correct_answer (int): Index of the correct answer (0-based)
        explanation (str): Optional explanation text shown after answering
        value (dict): Current state with 'selected', 'correct', and 'answered' keys
    """

    # Load JavaScript from external file
    _esm = Path(__file__).parent / "static" / "multiple-choice.js"

    # Traitlets
    question = traitlets.Unicode("").tag(sync=True)
    options = traitlets.List(trait=traitlets.Unicode()).tag(sync=True)
    correct_answer = traitlets.Int(0).tag(sync=True)
    explanation = traitlets.Unicode("").tag(sync=True)
    lang = traitlets.Unicode("en").tag(sync=True)
    value = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True)

    def __init__(self, question: str, options: list[str], correct_answer: int, explanation: str = "", lang: str = "en", **kwargs):
        """
        Initialize a multiple choice widget.

        Args:
            question: The question text
            options: List of answer options
            correct_answer: Index of the correct answer (0-based)
            explanation: Optional explanation text
        """
        super().__init__(**kwargs)
        self.question = question
        self.options = options
        self.correct_answer = correct_answer
        self.explanation = explanation
        self.lang = lang

__init__(question, options, correct_answer, explanation='', lang='en', **kwargs)

Initialize a multiple choice widget.

Parameters:

Name Type Description Default
question str

The question text

required
options list[str]

List of answer options

required
correct_answer int

Index of the correct answer (0-based)

required
explanation str

Optional explanation text

''
Source code in src/marimo_learn/multiple_choice.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def __init__(self, question: str, options: list[str], correct_answer: int, explanation: str = "", lang: str = "en", **kwargs):
    """
    Initialize a multiple choice widget.

    Args:
        question: The question text
        options: List of answer options
        correct_answer: Index of the correct answer (0-based)
        explanation: Optional explanation text
    """
    super().__init__(**kwargs)
    self.question = question
    self.options = options
    self.correct_answer = correct_answer
    self.explanation = explanation
    self.lang = lang

OrderingWidget

Bases: AnyWidget

An ordering question widget where students arrange items in sequence using drag-and-drop.

Attributes:

Name Type Description
question str

The question text to display

items list

Items in the correct order

shuffle bool

Whether to shuffle items initially

value dict

Current state with 'order' and 'correct' keys

Source code in src/marimo_learn/ordering.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class OrderingWidget(anywidget.AnyWidget):
    """
    An ordering question widget where students arrange items in sequence using drag-and-drop.

    Attributes:
        question (str): The question text to display
        items (list): Items in the correct order
        shuffle (bool): Whether to shuffle items initially
        value (dict): Current state with 'order' and 'correct' keys
    """

    # Load JavaScript from external file
    _esm = Path(__file__).parent / "static" / "ordering.js"

    # Traitlets
    question = traitlets.Unicode("").tag(sync=True)
    items = traitlets.List(trait=traitlets.Unicode()).tag(sync=True)
    current_order = traitlets.List(trait=traitlets.Unicode()).tag(sync=True)
    shuffle = traitlets.Bool(True).tag(sync=True)
    lang = traitlets.Unicode("en").tag(sync=True)
    value = traitlets.Dict(default_value=None, allow_none=True).tag(sync=True)

    def __init__(self, question: str, items: list[str], shuffle: bool = True, lang: str = "en", **kwargs):
        """
        Initialize an ordering widget.

        Args:
            question: The question text
            items: Items in the correct order
            shuffle: Whether to shuffle items initially (default: True)
        """
        super().__init__(**kwargs)
        self.question = question
        self.items = items
        self.shuffle = shuffle
        self.lang = lang

        # Create shuffled initial order if requested
        if shuffle:
            current = items.copy()
            random.shuffle(current)
            self.current_order = current
        else:
            self.current_order = items.copy()

__init__(question, items, shuffle=True, lang='en', **kwargs)

Initialize an ordering widget.

Parameters:

Name Type Description Default
question str

The question text

required
items list[str]

Items in the correct order

required
shuffle bool

Whether to shuffle items initially (default: True)

True
Source code in src/marimo_learn/ordering.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def __init__(self, question: str, items: list[str], shuffle: bool = True, lang: str = "en", **kwargs):
    """
    Initialize an ordering widget.

    Args:
        question: The question text
        items: Items in the correct order
        shuffle: Whether to shuffle items initially (default: True)
    """
    super().__init__(**kwargs)
    self.question = question
    self.items = items
    self.shuffle = shuffle
    self.lang = lang

    # Create shuffled initial order if requested
    if shuffle:
        current = items.copy()
        random.shuffle(current)
        self.current_order = current
    else:
        self.current_order = items.copy()

is_pyodide()

Is this notebook running in pyodide?

Source code in src/marimo_learn/utilities.py
 8
 9
10
11
def is_pyodide():
    """Is this notebook running in pyodide?"""

    return "pyodide" in sys.modules

localize_file(filepath)

Download a file from the 'public' directory, returning the local path.

Parameters:

Name Type Description Default
filepath str

path relative to 'public' directory

required

Returns:

Type Description
str

local file path

Raises:

Type Description
FileNotFoundError

if remote file not found

Source code in src/marimo_learn/utilities.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def localize_file(filepath: str) -> str:
    """
    Download a file from the 'public' directory, returning the
    local path.

    Args:
        filepath: path relative to 'public' directory

    Returns:
        local file path

    Raises:
        FileNotFoundError: if remote file not found
    """

    if not is_pyodide():
        return str(mo.notebook_dir() / "public" / filepath)

    url = str(mo.notebook_location() / "public" / filepath)
    response = httpx.get(url)
    if response.status_code != 200:
        raise FileNotFoundError(f"unable to get {filepath} from {url}")

    local_path = mo.notebook_dir() / filepath
    local_path.parent.mkdir(parents=True, exist_ok=True)
    with open(local_path, "wb") as writer:
        writer.write(response.content)

    return str(local_path)