pykubegrader.build package#

Submodules#

pykubegrader.build.api_notebook_builder module#

class pykubegrader.build.api_notebook_builder.FastAPINotebookBuilder(notebook_path: str, temp_notebook: str | None = None, assignment_tag: str = '', require_key: bool = False, verbose: bool = False)[source]#

Bases: object

add_api_code() None[source]#
static add_import_statements_to_tests(cell_source: list[str], require_key: bool = False, assignment_tag=None) list[str][source]#

Adds the necessary import statements to the first cell of the notebook.

add_points_to_notebook() None[source]#
add_question_part_points_to_notebook() None[source]#
add_question_points_to_notebook() None[source]#
static add_text_after_double_hash(markdown_source, insert_text, hash_prefix='## ')[source]#

Adds insert_text immediately after the first ‘##’ in the first line that starts with ‘##’.

Args: - markdown_source (list of str): The list of lines in the markdown cell. - insert_text (str): The text to be inserted.

Returns: - list of str: The modified markdown cell content.

assignment_tag: str = ''#
compute_max_points_free_response() None[source]#
static conceal_tests(cell_source)[source]#

Takes a list of code lines, detects blocks between # BEGIN HIDE and # END HIDE, encodes them in Base64, and replaces them with an exec() statement.

Returns a new list of lines with the concealed blocks.

construct_first_cell_question_header(cell_dict: dict) list[str][source]#
static construct_graders(cell_dict: dict) list[str][source]#
static construct_question_info(cell_dict: dict) list[str][source]#
static construct_update_responses(cell_dict: dict) list[str][source]#
extract_assertion_test_source(cell, source)[source]#

Extracts assertion test information from a given code cell source.

This method processes the source code of a Jupyter notebook cell to extract logging variables, assertions, comments, and point values associated with test configurations. It identifies and processes assertion statements, ensuring proper handling of multi-line assertions and comments.

Parameters:
  • cell (dict) – A dictionary representing a Jupyter notebook cell.

  • source (str) – The source code of the cell as a string.

Returns:

A tuple containing:
  • logging_variables (list): A list of variables used for logging.

  • assertions (list): A list of assertion statements extracted from the source.

  • comments (list): A list of comments associated with the assertions.

  • points_value (float or None): The point value extracted from the source, or None if not found.

Return type:

tuple

Raises:

ValueError – If the points value cannot be converted to a float.

extract_first_cell() Any[source]#
static extract_log_variables(cell: dict) list[str][source]#

Extracts log variables from the first cell.

Parameters:

cell (dict) – A dictionary representing a notebook cell.

Returns:

A list of log variable names extracted from the cell.

Return type:

list[str]

static extract_question_information(source: str) tuple[str, str, str][source]#

Extracts question information from the given source string.

Parameters:

source (str) – The source string containing question information.

Returns:

A tuple containing the question name, question number, and question part.

Return type:

tuple[str, str, str]

find_first_markdown_cell_with(start_index: int, end_index: int = 0, code_to_find: str = '## ')[source]#

Finds the first markdown cell going backwards from the given start_index to the given end_index where the first line starts with ‘##’.

Args: - start_index (int): The index to start searching from. - end_index (int): The index to stop searching at.

Returns: - tuple: The index of the found markdown cell and its source content.

static find_last_import_line(cell_source: list[str]) int[source]#

Finds the index of the last line with an import statement in a list of code lines, including multiline import statements.

Parameters:

cell_source (list) – List of strings representing the code lines.

Returns:

The index of the last line with an import statement, or -1 if no import is found.

Return type:

int

find_question_description(search_string)[source]#
get_cell(cell_index: int) Any[source]#
get_cell_source(notebook_path, cell_index)[source]#
static get_filename_and_root(path: str) tuple[Path, str][source]#
get_max_question_points(cell_dict) float[source]#
static get_question_points_by_part(question_dict: dict) dict[source]#

Get the points for each part of a question.

static insert_list_at_index(original_list: list[str], insert_list: list[str], index: int, line_break: bool = True, inplace_line_break: bool = True) list[str][source]#

Inserts a list into another list at a specific index.

Parameters:
  • original_list (list) – The original list.

  • insert_list (list) – The list to insert.

  • index (int) – The position at which to insert the new list.

Returns:

A single combined list with the second list inserted at the specified index.

Return type:

list

notebook_path: str#
question_dict() dict[source]#

Builds a dictionary of question information from the notebook.

Returns:

A dictionary containing question information.

Return type:

dict

read_notebook(notebook_path)[source]#
replace_cell_source(cell_index: int, new_source: str | list[str]) None[source]#

Replace the source code of a specific Jupyter notebook cell.

Parameters:
  • cell_index (int) – Index of the cell to be modified (0-based).

  • new_source (str) – New source code to replace the cell’s content.

require_key: bool = False#
run() None[source]#
static split_list_at_marker(input_list: list[str], marker: str = '# END TEST CONFIG') tuple[list[str], list[str]][source]#

Splits a list into two parts at the specified marker string.

Parameters:
  • input_list (list) – The list to split.

  • marker (str) – The string at which to split the list.

Returns:

A tuple containing two lists. The first list contains the elements

before the marker, and the second list contains the elements after the marker (excluding the marker itself).

Return type:

tuple

static tag_questions(cells_dict: dict) dict[source]#

Adds ‘is_first’ and ‘is_last’ boolean flags to the cells based on their position within the group of the same question. All cells will have both flags.

Parameters:

cells_dict (dict) – A dictionary where keys are cell IDs and values are cell details.

Returns:

The modified dictionary with ‘is_first’ and ‘is_last’ flags added.

Return type:

dict

temp_notebook: str | None = None#
verbose: bool = False#

pykubegrader.build.build_folder module#

class pykubegrader.build.build_folder.NotebookProcessor(root_folder: str, assignment_tag: str = '', verbose: bool = False, log: bool = True, require_key: bool = False, bonus_points: float = 0)[source]#

Bases: object

A class for processing Jupyter notebooks in a directory and its subdirectories.

root_folder#

The root directory containing notebooks to process.

Type:

str

assignment_tag#

Tag for the assignment being processed.

Type:

str

solutions_folder#

The directory where processed notebooks and solutions are stored.

Type:

str

verbose#

Flag for verbose output to the console.

Type:

bool

log#

Flag to enable or disable logging.

Type:

bool

add_assignment()[source]#

Sends a POST request to add an assignment.

add_final_submission_cells(notebook_path: str, output_path: str) None[source]#

Adds final submission cells to the end of a Jupyter notebook.

Parameters:
  • notebook_path (str) – Path to the input notebook.

  • output_path (str) – Path to save the modified notebook.

static add_initialization_code(notebook_path, week, assignment_type, require_key=False, **kwargs)[source]#
add_notebook(notebook_title, total_points)[source]#

Sends a POST request to add a notebook.

add_submission_cells(notebook_path: str, output_path: str) None[source]#

Adds submission cells to the end of a Jupyter notebook.

Parameters:
  • notebook_path (str) – Path to the input notebook.

  • output_path (str) – Path to save the modified notebook.

static add_validate_block(notebook_path: str, require_key: bool, assignment_tag=None, **kwargs) None[source]#

Modifies the first code cell of a Jupyter notebook to add the validate_token call if require_key is True.

Parameters:
  • notebook_path (str) – The path to the notebook file to modify.

  • require_key (bool) – Whether to add the validate_token cell.

Returns:

None

static add_validate_token_cell(notebook_path: str, require_key: bool, **kwargs) None[source]#

Adds a new code cell at the top of a Jupyter notebook if require_key is True.

Parameters:
  • notebook_path (str) – The path to the notebook file to modify.

  • require_key (bool) – Whether to add the validate_token cell.

Returns:

None

assignment_tag: str = ''#
bonus_points: float = 0#
build_payload(yaml_content)[source]#

Reads YAML content for an assignment and returns Python variables.

Parameters:

yaml_content (str) – The YAML file path to parse.

Returns:

A dictionary containing the parsed assignment data.

Return type:

dict

build_payload_notebook(yaml_content, notebook_title, total_points)[source]#
check_if_file_in_folder(file)[source]#
extract_MCQ()[source]#

Extracts questions from markdown cells and organizes them as a nested dictionary, including subquestion numbers.

Parameters:

ipynb_file (str) – Path to the .ipynb file.

Returns:

A nested dictionary where the first-level key is the question name (text after ##),

and the value is a dictionary with keys: ‘name’, ‘subquestion_number’, ‘question_text’, ‘OPTIONS’, and ‘solution’.

Return type:

dict

free_response_parser(temp_notebook_path, notebook_subfolder, notebook_name)[source]#
static generate_solution_MCQ(data_list, output_file='output.py')[source]#

Generates a Python file with solutions and total points based on the input data. If the file already exists, it appends new solutions to the existing solution dictionary.

Parameters:
  • data_list (list) – A list of dictionaries containing question metadata.

  • output_file (str) – Path to the output Python file.

static has_assignment(notebook_path, *tags)[source]#

Determines if a Jupyter notebook contains any of the specified configuration tags.

This method checks for the presence of specific content in a Jupyter notebook to identify whether it includes any of the required headings or tags.

Parameters:
  • notebook_path (str) – The file path to the Jupyter notebook to be checked.

  • *tags (str) – Variable-length argument list of tags to search for. Defaults to (”# ASSIGNMENT CONFIG”,).

Returns:

True if the notebook contains any of the specified tags, False otherwise.

Return type:

bool

Dependencies:
  • The check_for_heading function must be implemented. It should search

for specific headings or content in a notebook file and return a boolean value indicating if any of the tags exist.

Example

def check_for_heading(notebook_path, keywords):

# Mock implementation of content check with open(notebook_path, ‘r’) as file:

content = file.read()

return any(keyword in content for keyword in keywords)

notebook_path = “path/to/notebook.ipynb” # Check for default tags contains_config = has_assignment(notebook_path) self._print_and_log(f”Contains assignment config: {contains_config}”)

# Check for custom tags contains_custom = has_assignment(notebook_path, “# CUSTOM CONFIG”, “# ANOTHER CONFIG”) self._print_and_log(f”Contains custom config: {contains_custom}”)

static json_serial(obj)[source]#

JSON serializer for objects not serializable by default.

log: bool = True#
static merge_metadata(raw, data)[source]#

Merges raw metadata with extracted question data.

This method combines metadata from two sources: raw metadata and question data. It ensures that the points associated with each question are appropriately distributed and added to the final merged metadata.

Parameters:
  • raw (list) – A list of dictionaries containing raw metadata. Each dictionary must have a ‘points’ key with a value that can be either a list of points or a string representing a single point value.

  • data (list) – A list of dictionaries containing extracted question data. Each dictionary represents a set of questions and their details.

Returns:

A list of dictionaries where each dictionary represents a question

with merged metadata and associated points.

Return type:

list

Raises:
  • KeyError – If ‘points’ is missing from any raw metadata entry.

  • IndexError – If the number of items in raw and data do not match.

Example

raw = [

{“points”: [1.0, 2.0]}, {“points”: “3.0”}

] data = [

{“Q1”: {“question_text”: “What is 2+2?”}}, {“Q2”: {“question_text”: “What is 3+3?”}}

] merged = merge_metadata(raw, data) print(merged) # Output: # [ # {“Q1”: {“question_text”: “What is 2+2?”, “points”: 1.0}}, # {“Q2”: {“question_text”: “What is 3+3?”, “points”: 3.0}} # ]

multiple_choice_parser(temp_notebook_path, new_notebook_path)[source]#
process_notebooks()[source]#

Recursively processes Jupyter notebooks in a given folder and its subfolders.

The function performs the following steps: 1. Iterates through all files within the root folder and subfolders. 2. Identifies Jupyter notebooks by checking file extensions (.ipynb). 3. Checks if each notebook contains assignment configuration metadata. 4. Processes notebooks that meet the criteria using otter assign or other defined steps.

Prerequisites:
  • The has_assignment method should be implemented to check if a notebook

contains the required configuration for assignment processing. - The _process_single_notebook method should handle the specific processing of a single notebook, including moving it to a new folder or running additional tools like otter assign.

Raises:

- OSError – If an issue occurs while accessing files or directories.

Example

class NotebookProcessor:
def __init__(self, root_folder):

self.root_folder = root_folder

def has_assignment(self, notebook_path):

# Implementation to check for assignment configuration return True # Replace with actual check logic

def _process_single_notebook(self, notebook_path):

# Implementation to process a single notebook self._print_and_log(f”Processing notebook: {notebook_path}”)

processor = NotebookProcessor(“/path/to/root/folder”) processor.process_notebooks()

static remove_assignment_config_cells(notebook_path)[source]#
static remove_empty_cells(notebook_path, output_path=None)[source]#

Removes empty cells from a Jupyter Notebook and saves the updated notebook.

Parameters:
  • notebook_path (str) – Path to the input Jupyter Notebook.

  • output_path (str) – Path to save the updated Jupyter Notebook. If None, it overwrites the original file.

static remove_postfix(dist_folder, suffix='_temp')[source]#
static replace_temp_in_notebook(input_file, output_file)[source]#

Replaces occurrences of ‘_temp.ipynb’ with ‘.ipynb’ in a Jupyter Notebook.

Parameters: input_file (str): Path to the input Jupyter Notebook file. output_file (str): Path to the output Jupyter Notebook file.

Returns: None: Writes the modified notebook to the output file.

static replace_temp_no_otter(input_file, output_file)[source]#
require_key: bool = False#
root_folder: str#
static run_otter_assign(notebook_path, dist_folder)[source]#

Runs otter assign on the given notebook and outputs to the specified distribution folder.

select_many_parser(temp_notebook_path, new_notebook_path)[source]#
solutions_folder: str#
true_false_parser(temp_notebook_path, new_notebook_path)[source]#
update_initialize_function()[source]#
verbose: bool = False#
pykubegrader.build.build_folder.check_for_heading(notebook_path, search_strings)[source]#

Checks if a Jupyter notebook contains a heading cell whose source matches any of the given strings.

pykubegrader.build.build_folder.clean_notebook(notebook_path)[source]#

Removes specific cells and makes Markdown cells non-editable and non-deletable by updating their metadata.

pykubegrader.build.build_folder.ensure_imports(output_file, header_lines)[source]#

Ensures specified header lines are present at the top of the file.

Parameters:
  • output_file (str) – The path of the file to check and modify.

  • header_lines (list of str) – Lines to ensure are present at the top.

Returns:

The existing content of the file (without the header).

Return type:

str

pykubegrader.build.build_folder.extract_MCQ(ipynb_file)[source]#

Extracts multiple-choice questions from markdown cells within sections marked by # BEGIN MULTIPLE CHOICE and # END MULTIPLE CHOICE.

Parameters:

ipynb_file (str) – Path to the .ipynb file.

Returns:

A list of dictionaries, where each dictionary corresponds to questions within

a section. Each dictionary contains parsed questions with details like ‘name’, ‘subquestion_number’, ‘question_text’, ‘OPTIONS’, and ‘solution’.

Return type:

list

pykubegrader.build.build_folder.extract_SELECT_MANY(ipynb_file)[source]#

Extracts questions marked by # BEGIN SELECT MANY and # END SELECT MANY in markdown cells, including all lines under the SOLUTION header until the first blank line or whitespace-only line.

Parameters:

ipynb_file (str) – Path to the .ipynb file.

Returns:

A list of dictionaries, where each dictionary corresponds to questions within

a section. Each dictionary contains parsed questions with details like ‘name’, ‘subquestion_number’, ‘question_text’, and ‘solution’.

Return type:

list

pykubegrader.build.build_folder.extract_TF(ipynb_file)[source]#

Extracts True False questions from markdown cells within sections marked by # BEGIN TF and # END TF.

Parameters:

ipynb_file (str) – Path to the .ipynb file.

Returns:

A list of dictionaries, where each dictionary corresponds to questions within

a section. Each dictionary contains parsed questions with details like ‘name’, ‘subquestion_number’, ‘question_text’, and ‘solution’.

Return type:

list

pykubegrader.build.build_folder.extract_config_from_notebook(notebook_path)[source]#

Extract configuration text from a Jupyter Notebook.

Parameters:

notebook_path (str) – Path to the Jupyter Notebook file.

Returns:

The configuration text if found, otherwise an empty string.

Return type:

str

pykubegrader.build.build_folder.extract_files(config_text)[source]#

Extract the list of files from the given configuration text, excluding .bin files.

Parameters:

config_text (str) – The configuration text to process.

Returns:

A list of file names excluding .bin files.

Return type:

list

pykubegrader.build.build_folder.extract_question(text)[source]#
pykubegrader.build.build_folder.extract_raw_cells(ipynb_file, heading='# BEGIN MULTIPLE CHOICE')[source]#

Extracts all metadata from value cells in a Jupyter Notebook file for a specified heading.

Parameters:
  • ipynb_file (str) – Path to the .ipynb file.

  • heading (str) – The heading to search for in value cells.

Returns:

A list of dictionaries containing extracted metadata for each heading occurrence.

Return type:

list of dict

pykubegrader.build.build_folder.find_first_code_cell(notebook_path)[source]#

Finds the first Python code cell in a Jupyter notebook and its index.

Parameters:

notebook_path (str) – Path to the Jupyter notebook file.

Returns:

A tuple containing the index of the first code cell and the cell dictionary,

or (None, None) if no code cell is found.

Return type:

tuple

pykubegrader.build.build_folder.generate_mcq_file(data_dict, output_file='mc_questions.py')[source]#

Generates a Python file defining an MCQuestion class from a dictionary.

Parameters:
  • data_dict (dict) – A nested dictionary containing question metadata.

  • output_file (str) – The path for the output Python file.

Returns:

None

pykubegrader.build.build_folder.generate_select_many_file(data_dict, output_file='select_many_questions.py')[source]#

Generates a Python file defining an MCQuestion class from a dictionary.

Parameters:
  • data_dict (dict) – A nested dictionary containing question metadata.

  • output_file (str) – The path for the output Python file.

Returns:

None

pykubegrader.build.build_folder.generate_tf_file(data_dict, output_file='tf_questions.py')[source]#

Generates a Python file defining an MCQuestion class from a dictionary.

Parameters:
  • data_dict (dict) – A nested dictionary containing question metadata.

  • output_file (str) – The path for the output Python file.

Returns:

None

pykubegrader.build.build_folder.main()[source]#
pykubegrader.build.build_folder.replace_cell_source(notebook_path, cell_index, new_source)[source]#

Replace the source code of a specific Jupyter notebook cell.

Parameters:
  • cell_index (int) – Index of the cell to be modified (0-based).

  • new_source (str) – New source code to replace the cell’s content.

pykubegrader.build.build_folder.replace_cells_between_markers(data, markers, ipynb_file, output_file)[source]#

Replaces the cells between specified markers in a Jupyter Notebook (.ipynb file) with provided replacement cells and writes the result to the output file.

Parameters: data (list): A list of dictionaries with data for creating replacement cells. markers (tuple): A tuple containing two strings: the BEGIN and END markers. ipynb_file (str): Path to the input Jupyter Notebook file. output_file (str): Path to the output Jupyter Notebook file.

Returns: None: Writes the modified notebook to the output file.

pykubegrader.build.build_folder.sanitize_string(input_string)[source]#

Converts a string into a valid Python variable name.

Parameters:

input_string (str) – The string to convert.

Returns:

A valid Python variable name.

Return type:

str

pykubegrader.build.build_folder.update_initialize_assignment(notebook_path: str, assignment_points: float | None = None, assignment_tag: str | None = None) None[source]#

Search for a specific line in a Jupyter Notebook and update it with additional input variables.

Parameters:
  • notebook_path (str) – The path to the Jupyter Notebook file (.ipynb).

  • assignment_points (Optional[float]) – The assignment points variable to add (default is None).

  • assignment_tag (Optional[str]) – The assignment tag variable to add (default is None).

Returns:

None

pykubegrader.build.clean_folder module#

class pykubegrader.build.clean_folder.FolderCleaner(root_folder: str)[source]#

Bases: object

delete_dist_folders()[source]#

Recursively deletes all folders named ‘dist’ starting from the root folder.

pykubegrader.build.clean_folder.main()[source]#

pykubegrader.build.collate module#

class pykubegrader.build.collate.QuestionCollator(root_folder: str, output_path: str)[source]#

Bases: object

collate_questions()[source]#

Collates questions from all solution folders and saves them to a new notebook.

create_collated_notebook(questions)[source]#

Creates a new notebook with questions organized by type.

Parameters:

questions (dict) – Dictionary of categorized questions.

Returns:

The collated notebook.

Return type:

Notebook

extract_questions(folder_path)[source]#

Extracts questions from all notebooks in the solution folder.

Parameters:

folder_path (str) – Path to the solution folder.

Returns:

Dictionary of categorized questions.

Return type:

dict

find_solution_folders()[source]#

Finds all immediate subdirectories inside ‘_solution*’ folders that contain notebooks.

Returns:

List of folder paths containing notebooks.

Return type:

list

save_notebook(nb)[source]#

Saves the collated notebook to the specified output path.

Parameters:

nb (Notebook) – The notebook to save.

pykubegrader.build.collate.main()[source]#

pykubegrader.build.markdown_questions module#

class pykubegrader.build.markdown_questions.MarkdownToNotebook(markdown_file: str)[source]#

Bases: object

convert_and_save()[source]#

Converts the Markdown file into a Jupyter Notebook and saves it with the same name.

static modify_notebook(notebook_path: str)[source]#

Modifies an existing Jupyter Notebook by converting cells containing specific markers (”# BEGIN T”, “# END”, “# ASSIGNMENT CONFIG”) into raw cells.

Parameters:

notebook_path (str) – Path to the existing Jupyter Notebook to modify.

pykubegrader.build.markdown_questions.main()[source]#

Module contents#