diff --git a/rudaux/flows.py b/rudaux/flows.py index c343dd2..e6731e7 100644 --- a/rudaux/flows.py +++ b/rudaux/flows.py @@ -1,17 +1,17 @@ -import sys, os +import sys +import os import prefect -from prefect import Flow, unmapped, task, flatten +from prefect import Flow, unmapped, task from prefect.engine import signals from prefect.schedules import IntervalSchedule -from prefect.executors import LocalExecutor, DaskExecutor, LocalDaskExecutor +from prefect.executors import LocalExecutor from prefect.backend import FlowView, FlowRunView from prefect.tasks.control_flow.filter import FilterTask from traitlets.config import Config from traitlets.config.loader import PyFileConfigLoader import pendulum as plm from requests.exceptions import ConnectionError -import logging -from subprocess import check_output, STDOUT, CalledProcessError +from subprocess import check_output, CalledProcessError import threading @@ -28,16 +28,17 @@ __PROJECT_NAME = "rudaux" + def _build_flows(args): print("Loading the rudaux_config.py file...") if not os.path.exists(os.path.join(args.directory, 'rudaux_config.py')): - sys.exit( - f""" - There is no rudaux_config.py in the directory {args.directory}, - and no course directory was specified on the command line. Please - specify a directory with a valid rudaux_config.py file. - """ - ) + sys.exit( + f""" + There is no rudaux_config.py in the directory {args.directory}, + and no course directory was specified on the command line. Please + specify a directory with a valid rudaux_config.py file. + """ + ) config = Config() config.merge(PyFileConfigLoader('rudaux_config.py', path=args.directory).load_config()) @@ -70,12 +71,12 @@ def _build_flows(args): flows = [] for build_func, flow_name, interval, minute in flow_builders: print(f"Building/registering the {flow_name} flow...") - _flows = build_func(config, args) + _flows = build_func(config) for flow in _flows: flow.executor = executor if not config.debug: - flow.schedule = IntervalSchedule(start_date = plm.now('UTC').set(minute=minute), - interval = plm.duration(minutes=interval)) + flow.schedule = IntervalSchedule(start_date=plm.now('UTC').set(minute=minute), + interval=plm.duration(minutes=interval)) flows.append(flow) return flows @@ -100,14 +101,13 @@ def register(args): except ConnectionError as e: print(e) sys.exit( - f""" - Could not connect to the prefect server. Is the server running? - Make sure to start the server before trying to register flows. - To start the prefect server, run the command: - - prefect server start + """ + Could not connect to the prefect server. Is the server running? + Make sure to start the server before trying to register flows. + To start the prefect server, run the command: - """ + prefect server start + """ ) flows = _build_flows(args) for flow in flows: @@ -135,7 +135,7 @@ def run(args): return -def build_snapshot_flows(config, args): +def build_snapshot_flows(config): flows = [] for group in config.course_groups: for course_id in config.course_groups[group]: @@ -155,16 +155,31 @@ def build_snapshot_flows(config, args): flows.append(flow) return flows + @task(checkpoint=False) def combine_dictionaries(dicts): - return {k : v for d in dicts for k, v in d.items()} + return {k: v for d in dicts for k, v in d.items()} -def build_autoext_flows(config, args): + +def build_autoext_flows(config): + """ + Build the flow for the auto-extension of assignments for students + who register late. + + Params + ------ + config: traitlets.config.loader.Config + a dictionary-like object containing the configurations + from rudaux_config.py + """ flows = [] for group in config.course_groups: for course_id in config.course_groups[group]: - with Flow(config.course_names[course_id]+"-autoext", terminal_state_handler = fail_handler_gen(config)) as flow: + with Flow(config.course_names[course_id] + "-autoext", + terminal_state_handler=fail_handler_gen(config)) as flow: + assignment_names = list(config.assignments[group].keys()) + # Obtain course/student/assignment/etc info from the course API course_info = api.get_course_info(config, course_id) assignments = api.get_assignments(config, course_id, assignment_names) @@ -178,7 +193,7 @@ def build_autoext_flows(config, args): submission_sets = subm.build_submission_set.map(unmapped(config), submission_sets) # Compute override updates - overrides = subm.get_latereg_overrides.map(unmapped(config.latereg_extension_days[group]), submission_sets) + overrides = subm.get_latereg_overrides.map(unmapped(config.latereg_extension_days[group]), submission_sets, unmapped(config)) # TODO: we would ideally do flatten(overrides) and then # api.update_override.map(unmapped(config), unmapped(course_id), flatten(overrides)) @@ -196,7 +211,7 @@ def build_autoext_flows(config, args): # rather dynamically from LMS; there we dont know what # assignments there are until runtime. So doing it by group is the # right strategy. -def build_grading_flows(config, args): +def build_grading_flows(config): try: check_output(['sudo', '-n', 'true']) except CalledProcessError as e: diff --git a/rudaux/submission.py b/rudaux/submission.py index 99c30e3..11cf3b7 100644 --- a/rudaux/submission.py +++ b/rudaux/submission.py @@ -212,7 +212,7 @@ def generate_latereg_overrides_name(extension_days, subm_set, **kwargs): return 'lateregs-'+subm_set['__name__'] @task(checkpoint=False,task_run_name=generate_latereg_overrides_name) -def get_latereg_overrides(extension_days, subm_set): +def get_latereg_overrides(extension_days, subm_set, config): logger = get_logger() fmt = 'ddd YYYY-MM-DD HH:mm:ss' overrides = [] @@ -234,7 +234,7 @@ def get_latereg_overrides(extension_days, subm_set): to_remove = None to_create = None - if regdate > assignment['unlock_at']: + if regdate > assignment['unlock_at'] and assignment['unlock_at'] <= plm.from_format(config.registration_deadline, f'YYYY-MM-DD', tz=config.notify_timezone): #the late registration due date latereg_date = regdate.add(days=extension_days).in_timezone(tz).end_of('day').set(microsecond=0) if latereg_date > subm['due_at']: diff --git a/scripts/rudaux_config_template.py b/scripts/rudaux_config_template.py index 9f89f96..d21f514 100644 --- a/scripts/rudaux_config_template.py +++ b/scripts/rudaux_config_template.py @@ -1,30 +1,34 @@ -import rudaux - - # the base domain for canvas at your institution # e.g. at UBC, this is https://canvas.ubc.ca c.canvas_domain = 'https://canvas.your-domain.com' # tells rudaux which courses are part of which groups -#group_name is a simple name for the group of canvas courses. -#e.g. +# group_name is a simple name for the group of canvas courses. +# e.g. # "dsci100" : ["12345", "678910"] # "stat201" : ["54323"] c.course_groups = { - 'group_name' : ['canvas_id_1', 'canvas_id_2'] + 'group_name': ['canvas_id_1', 'canvas_id_2'] } # tells rudaux what to call each course when printing to logs # canvas_id_1/2/etc are the same as above -# human_readable_name_1/2/etc is just a human-readable name for each section, +# human_readable_name_1/2/etc is just a human-readable name for each section +# (e.g., human_readable_name_1 and 2 would be dsci100-001 and dsci100-004 for +# the two dsci100 sections) # make sure you include names for every canvas ID above -# e.g. +# e.g. # {"12345" : "dsci100-001", "678910" : "dsci100-004"} c.course_names = { - 'canvas_id_1' : 'human_readable_name_1', #e.g. human_readable_name_1 and 2 would be dsci100-001 and dsci100-004 (for the two dsci100 sections) - 'canvas_id_2' : 'human_readable_name_2', + 'canvas_id_1': 'human_readable_name_1', + 'canvas_id_2': 'human_readable_name_2', } +# tells rudaux the last day students have to add a course. +# at UBC we can check it here: +# https://students.ubc.ca/enrolment/registration/course-change-dates +c.registration_deadline = '2022-01-21' + # gives rudaux the ability to read/write to canvas page # canvas_id_1/2/etc are same as above # instructor_token_1/2/etc are the Canvas API tokens for the instructor of each section