Source code for abcclassroom.clone

"""
abc-classroom.clone
======================

"""

import csv
from pathlib import Path
import shutil

from . import config as cf
from . import git as abcgit


[docs]def clone_or_update_repo(organization, repo, clone_dir, skip_existing): """ Tries to clone the single repository 'repo' from the organization. If the local repository already exists, pulls instead of cloning (unless the skip flag is set, in which case it does nothing). Parameters ---------- organization : string Organization where your GitHub classroom lives. repo : string Name of the student's GitHub repo. clone_dir : string Name of the clone directory. skip_existing : boolean True if you wish to skip copying files to existing repos. """ destination_dir = Path(clone_dir, repo) if destination_dir.is_dir(): # if path exists, pull instead of clone (unless skip_existing) if skip_existing: print( "Local repo {} already exists; skipping".format( destination_dir ) ) return try: abcgit.pull_from_github(destination_dir) except RuntimeError as e: print("Error pulling repository {}".format(destination_dir)) print(e) else: try: abcgit.clone_repo(organization, repo, clone_dir) except RuntimeError as e: print("Error cloning repository {}".format(repo)) print(e)
[docs]def clone_student_repos(args): """This is the CLI implementation of clone repos Parameters ---------- args : string argument inputs Arguments include the assignment name (string) and skip existing ( boolean?) """ assignment_name = args.assignment skip_existing = args.skip_existing no_submitted = args.no_submitted clone_repos(assignment_name, skip_existing, no_submitted)
[docs]def clone_repos(assignment_name, skip_existing=False, no_submitted=True): """Iterates through the student roster, clones each repo for this assignment into the directory specified in the config, and then copies the notebook files into the 'course_materials/submitted' directory, based on course_materials set in config.yml. Parameters ---------- assignment_name : string The name of the assignment to clone repos for skip_existing : boolean (default=False) Do not update files in repositories that have already been cloned. no_submitted : boolean (default = True) If true, moves assignment files from cloned repo to submitted directory for grading. If false files are not moved to submitted dir. This might be useful if you want to update the student clone but don't want to update the file that was parsed by the autograder Returns -------- This returns the cloned student repos and also moves each notebook file into the nbgrader "submitted" directory. """ print("Loading configuration from config.yml") try: config = cf.get_config() except (FileNotFoundError, RuntimeError) as err: print(err) return roster_filename = cf.get_config_option(config, "roster", True) course_dir = cf.get_config_option(config, "course_directory", True) clone_dir = cf.get_config_option(config, "clone_dir", True) organization = cf.get_config_option(config, "organization", True) materials_dir = cf.get_config_option(config, "course_materials", False) if materials_dir is None: print( "Oops! I couldn't find a course_materials directory location " "in your config.yml file. I will only clone all of the student" "repositories. I can not copy any assignment files to a " "course_materials directory given it does not exist." ) try: # Create the assignment subdirectory path and ensure it exists Path(course_dir, clone_dir, assignment_name).mkdir(exist_ok=True) missing_repos = [] missing_student_gh = [] with open(roster_filename, newline="") as csvfile: reader = csv.DictReader(csvfile) try: for row in reader: student = row["github_username"] print(student) # If there is no student gh name skip trying to clone if not student: missing_student_gh.append(row) else: # Expected columns: identifier,github_username, # github_id,name repo = "{}-{}".format(assignment_name, student) try: clone_or_update_repo( organization, repo, Path(clone_dir, assignment_name), skip_existing, ) if materials_dir is not None and no_submitted: copy_assignment_files( config, student, assignment_name ) print( "Copying files to the:", materials_dir, "dir", ) else: print("Not copying files to submitted") except RuntimeError: missing_repos.append(repo) except KeyError as ke: raise KeyError( "Oops! Please check your roster file to " "ensure is has the correct " "headers. {}".format(ke) ) if len(missing_repos) == 0 and len(missing_student_gh) == 0: print("Great! All repos were successfully cloned!") else: # Two potential points of failure 1. github repo doesn't exist or # 2. missing gh username. Here the message is clear about what # is wrong if len(missing_repos) > 0: print("Could not clone or update the following repos: ") for r in missing_repos: print(" {}".format(r)) if len(missing_student_gh) > 0: print( "Oops! The following students are missing github " "usernames in the roster. Consider adding their username " "to your roster.csv file or removing that entry from the " "file altogether." ) for astudent in missing_student_gh: print(" {}".format(astudent)) except FileNotFoundError as err: abs_roster_path = Path(roster_filename).resolve() raise FileNotFoundError( "Cannot find roster file: {}".format(abs_roster_path) ) print(err)
[docs]def copy_assignment_files(config, student, assignment_name): """Copies all notebook files from clone_dir to course_materials/submitted. Will overwrite any existing files with the same name. Parameters ----------- config: dict config file returned as a dictionary from get_config() student: string Name of the student whose files are being copied assignment_name: string Name of the assignment for which files are being copied """ course_dir = cf.get_config_option(config, "course_directory", True) materials_dir = cf.get_config_option(config, "course_materials", False) clone_dir = cf.get_config_option(config, "clone_dir", True) files_to_grade = cf.get_config_option(config, "files_to_grade", False) repo = "{}-{}".format(assignment_name, student) # Copy files from the cloned_dirs to submitted directory source_dir = Path(course_dir, clone_dir, assignment_name, repo) destination = Path( course_dir, materials_dir, "submitted", student, assignment_name ) destination.mkdir(parents=True, exist_ok=True) print("Copying files from {} to {}".format(Path(source_dir), destination)) # Only move files with extensions needed for grading # NOTE: if there is a notebook or script in a subdirectory shutil does not # handle the subdirectory - it spits the file back into the main dir. for a_file in Path(course_dir, clone_dir, assignment_name, repo).glob( r"**/*" ): # If files to grade is not populated then just move notebooks if not files_to_grade: files_to_grade = [".ipynb"] if a_file.suffix in files_to_grade: print("copying {} to {}".format(a_file, destination)) shutil.copy(a_file, destination)
# In this case, IF you have a graded html file that will get moved over # Using the copytree function from util to make copying easier # This also moves subdirectories by default # shutil.copytree( # source_dir, # destination, # ignore=shutil.ignore_patterns(*ignore_files), # dirs_exist_ok=True, # )