Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b2e7827
added ordering to the questions
HarrySu123 Jul 28, 2025
85fdc12
renamed all use of module with set
HarrySu123 Jul 29, 2025
70354e7
included minimal json template for set in json converter
HarrySu123 Jul 30, 2025
14c8958
implemented sorted imports
HarrySu123 Jul 30, 2025
72ccbd7
refactoring and now allows setting name and description for the set
HarrySu123 Jul 30, 2025
9ef11ad
updated yml file
HarrySu123 Jul 30, 2025
40dbc83
removed virtualenv from the test.yml file
HarrySu123 Jul 30, 2025
39f5193
used a safer version of requests
HarrySu123 Jul 31, 2025
bfdacdf
corrected formatted and sorted imports in PartsSepSol filter
HarrySu123 Jul 31, 2025
6881859
ran isort . and black . to correctly format everything
HarrySu123 Jul 31, 2025
722261b
further fixing of formatting in function summaries and description
HarrySu123 Jul 31, 2025
2ea43c9
removed blank line after function docstring in set.py
HarrySu123 Jul 31, 2025
c172a9f
correected tests to match new results
HarrySu123 Jul 31, 2025
49dcd3c
added attributes for modifable visiblity for finalanswers , wokred so…
HarrySu123 Aug 4, 2025
18c17dc
added visibility settings to API
HarrySu123 Aug 4, 2025
47777e8
corretly formatted and sorted imports in set.py
HarrySu123 Sep 1, 2025
b46ce09
corrected formatting on import in set.py
HarrySu123 Sep 1, 2025
c93ba77
resolved isort and black conflict
HarrySu123 Sep 1, 2025
1c7aa90
Added missing docstrings for methods in visibility_status
HarrySu123 Sep 1, 2025
98c92e4
added module-level docstring for visibility_status
HarrySu123 Sep 1, 2025
36f8e3c
filename generation now handles special characters correctly
HarrySu123 Sep 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade setuptools wheel
pip install poetry
poetry config virtualenvs.create false
poetry install
poetry config virtualenvs.create true
poetry install --with dev
- name: Install Pandoc # apt version seems too old
uses: r-lib/actions/setup-pandoc@v2
- name: Linting Checks
run: |
black --check .
isort --check-only in2lambda docs
pydocstyle --convention=google in2lambda
poetry run black .
poetry run isort --check-only in2lambda docs
poetry run pydocstyle --convention=google in2lambda
- name: pytest
run: pytest --cov-report=xml:coverage.xml --cov=in2lambda --doctest-modules in2lambda
run: poetry run pytest --cov-report=xml:coverage.xml --cov=in2lambda --doctest-modules in2lambda
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
Expand Down
6 changes: 6 additions & 0 deletions .metals/metals.lock.db
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#FileLock
#Mon Aug 04 15:03:28 BST 2025
hostName=localhost
id=19860ab936622402fe2f44aa1920af12cbe404b7acd
method=file
server=localhost\:45175
2 changes: 1 addition & 1 deletion docs/source/contributing/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,5 @@ The [CLT reference](../reference/command-line) is generated based on the output
[API documentation](../reference/library) is built based on [docstrings](https://peps.python.org/pep-0257/#what-is-a-docstring) found within the code base. This is done using [sphinx.ext.autosummary](https://www.sphinx-doc.org/en/master/usage/extensions/autosummary.html).

:::{tip}
See the [Module docs](../reference/_autosummary/in2lambda.api.module.Module) and the source code buttons on its page for good examples on how to write the docstrings effectively.
See the [Set docs](../reference/_autosummary/in2lambda.api.set.Set) and the source code buttons on its page for good examples on how to write the docstrings effectively.
:::
125 changes: 0 additions & 125 deletions in2lambda/api/module.py

This file was deleted.

2 changes: 1 addition & 1 deletion in2lambda/api/question.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""A full question with optional parts that's contained in a module."""
"""A full question with optional parts that's contained in a set."""

from dataclasses import dataclass, field
from typing import Union
Expand Down
185 changes: 185 additions & 0 deletions in2lambda/api/set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"""Represents a list of questions."""

from dataclasses import dataclass, field
from typing import Union

import panflute as pf

from in2lambda.api.question import Question
from in2lambda.api.visibility_status import VisibilityController, VisibilityStatus


@dataclass
class Set:
"""Represents a list of questions."""

_name: str = field(default="set")
_description: str = field(default="")
_finalAnswerVisibility: VisibilityController = field(
default_factory=lambda: VisibilityController(
VisibilityStatus.OPEN_WITH_WARNINGS
)
)
_workedSolutionVisibility: VisibilityController = field(
default_factory=lambda: VisibilityController(
VisibilityStatus.OPEN_WITH_WARNINGS
)
)
_structuredTutorialVisibility: VisibilityController = field(
default_factory=lambda: VisibilityController(VisibilityStatus.OPEN)
)

questions: list[Question] = field(default_factory=list)
_current_question_index = -1

@property
def current_question(self) -> Question:
"""The current question being modified, or Question("INVALID") if there are no questions.
The reasoning behind returning Question("INVALID") is in case filter logic is being applied
on text before the first question (e.g. intro paragraphs). In that case, there is no effect.
Returns:
The current question or Question("INVALID") if there are no questions.
Examples:
>>> from in2lambda.api.set import Set
>>> Set().current_question
Question(title='INVALID', parts=[], images=[], main_text='')
>>> s = Set()
>>> s.add_question()
>>> s.current_question
Question(title='', parts=[], images=[], main_text='')
"""
return (
self.questions[self._current_question_index]
if self.questions
else Question("INVALID")
)

def add_question(
self, title: str = "", main_text: Union[pf.Element, str] = pf.Str("")
) -> None:
"""Inserts a new question into the set.
Args:
title: An optional string for the title of the question. If no title
is provided, the question title auto-increments i.e. Question 1, 2, etc.
main_text: An optional string or panflute element for the main question text.
Examples:
>>> from in2lambda.api.set import Set
>>> import panflute as pf
>>> s = Set()
>>> s.add_question("Some title", pf.Para(pf.Str("hello"), pf.Space, pf.Str("there")))
>>> s.questions
[Question(title='Some title', parts=[], images=[], main_text='hello there')]
>>> s.add_question(main_text="Normal string text")
>>> s.questions[1].main_text
'Normal string text'
"""
question = Question(title=title)
question.main_text = main_text
self.questions.append(question)

def increment_current_question(self) -> None:
"""Manually overrides the current question being modified.
The default (-1) indicates the last question added. Incrementing for the
first time sets to 0 i.e. the first question.
The is useful if adding question text first and answers later.
Examples:
>>> from in2lambda.api.set import Set
>>> s = Set()
>>> # Imagine adding the questions from a question file first...
>>> s.add_question("Question 1")
>>> s.add_question("Question 2")
>>> # ...and then adding solutions from an answer file later
>>> s.increment_current_question() # Loop back to question 1
>>> s.current_question.add_solution("Question 1 answer")
>>> s.increment_current_question()
>>> s.current_question.add_solution("Question 2 answer")
>>> s.questions
[Question(title='Question 1', parts=[Part(text='', worked_solution='Question 1 answer')], images=[], main_text=''),\
Question(title='Question 2', parts=[Part(text='', worked_solution='Question 2 answer')], images=[], main_text='')]
"""
self._current_question_index += 1

def to_json(self, output_dir: str) -> None:
"""Turns this set into Lambda Feedback JSON/ZIP files.
WARNING: This will overwrite any existing files in the directory.
Args:
output_dir: Where to output the final Lambda Feedback JSON/ZIP files.
Examples:
>>> import tempfile
>>> import os
>>> import json
>>> # Create a set with two questions
>>> s = Set()
>>> s.add_question("Question 1")
>>> s.add_question("Question 2")
>>> with tempfile.TemporaryDirectory() as temp_dir:
... # Write the JSON files to the temporary directory
... s.to_json(temp_dir)
... # Check the contents of the directory
... sorted(os.listdir(temp_dir))
... # Check the contents of the set directory
... sorted(os.listdir(f"{temp_dir}/set"))
... # Check the title of the first question
... with open(f"{temp_dir}/set/question_000_Question_1.json") as file:
... print(f"Question 1's title: {json.load(file)['title']}")
['set', 'set.zip']
['question_000_Question_1.json', 'question_001_Question_2.json', 'set_set.json']
Question 1's title: Question 1
"""
from in2lambda.json_convert import json_convert

json_convert.main(self, output_dir)

def set_name(self, name: str) -> None:
"""Sets the name of the set.
Args:
name: The name to set for the set.
Examples:
>>> from in2lambda.api.set import Set
>>> s = Set()
>>> s.set_name("My Question Set")
>>> s._name
'My Question Set'
"""
self._name = name

def set_description(self, description: str) -> None:
"""Sets the description of the set.
Args:
description: The description to set for the set.
Examples:
>>> from in2lambda.api.set import Set
>>> s = Set()
>>> s.set_description("This is my question set.")
>>> s._description
'This is my question set.'
"""
self._description = description

def __repr__(self):
"""Custom representation showing visibility status values instead of memory addresses."""
return (
f"Set("
f"_name={self._name!r}, "
f"_description={self._description!r}, "
f"_finalAnswerVisibility={str(self._finalAnswerVisibility)!r}, "
f"_workedSolutionVisibility={str(self._workedSolutionVisibility)!r}, "
f"_structuredTutorialVisibility={str(self._structuredTutorialVisibility)!r}, "
f"questions={self.questions!r}"
f")"
)
Loading