SRE.params
1import os 2import re 3from datetime import datetime 4from typing import Literal 5 6from pathlib import Path 7 8sre_uid = 1100 9sre_gid = 1100 10sre_user = "sre" 11docker_gid = 988 12 13admin_uids = [] 14admin_gids = [2000] 15 16main_sre_dir = "/opt/sre" 17 18# Override main_sre_dir with the actual install root derived from this file's 19# location, so a single build/checkout works from any install path. Without 20# this, lib_dir stays at the build-time value and `from utils import ...` in 21# srelab.py files fails when sre is installed somewhere other than /opt/sre. 22# The marker file (src/sre.py) sits in a world-readable directory so this 23# probe works whether we're imported by the privileged CLI or by sysreseval 24# running as an unprivileged student (lab/ and lib/ are sre:sre-only). 25_candidate = Path(__file__).resolve().parent.parent.parent 26if (_candidate / "src" / "sre.py").is_file(): 27 main_sre_dir = str(_candidate) 28del _candidate 29sre_pub_dir = "/var/lib/sre" 30sre_user_public_dir = "/home/sre" 31 32lab_dir = main_sre_dir + "/lab" 33lib_dir = main_sre_dir + "/lib" 34 35 36 37 38# debug_mode: 39# - allow environment variables SRE_WRAPPER and SRE_PUB_DIR to overcome the default value (insecure) 40# - allow "sysreseval --debug" 41debug_mode = True #False 42 43# If True, sysreseval will use the last field in the gecos string and save it in the answers 44# of the user as email (to be used in the pdfs generated with "sre outline") 45email_in_gecos_last_field: bool = True 46 47max_docker_concurrency = 16 48 49display_marks_in_auto_evaluations_by_default = False 50use_numerical_marks_by_default = True 51default_maximum_mark = 20 52 53# Scope bitmask values for GradeElement.scope. A grade element is visible in 54# self-eval (sre eval --auto-eval) if scope & SELF_EVAL_SCOPE, and in non-auto 55# eval / outline / sheet if scope & EXO_EVAL_SCOPE. 56SELF_EVAL_SCOPE = 1 57EXO_EVAL_SCOPE = 2 58BOTH_EVAL_SCOPE = SELF_EVAL_SCOPE | EXO_EVAL_SCOPE # 3 59grade_scopes = (SELF_EVAL_SCOPE, EXO_EVAL_SCOPE, BOTH_EVAL_SCOPE) 60 61default_eval_interval_during_exams = 60 # default duration between two evaluations in exam mode 62default_eval_interval_without_exam_mode = 60 # default interval (seconds) for eval outside exam mode when the lab does not define eval_interval_without_exam_mode (0 for no periodic evals) 63default_save_record_interval_during_exams = 60 # use 0 to disable saving 64 65default_exam_duration = 90 # default exam duration in minutes when "duration" is absent from exam.json 66 67 68default_inactivity_threshold_in_watch_command = 90 # default inactivity (no eval) from a project before triggering an alert in watch 69default_dashboard_refresh_interval_in_watch_command = 1 70 71max_duration_between_exam_pre_start_and_start = 60 # duration in seconds 72 73default_exec_shell = "/bin/bash" 74 75################################################################################# 76# 77# Security settings 78# 79################################################################################# 80 81allow_privileged_machines = True 82disable_volume_mount_on_root_partition = False 83 84# For identifying the users, use the environment variable SUDO_USER instead of USER_USERNAME 85# SUDO_USER is set by sudo itself, thus not spoofable (but in some configurations don't give the current USER variable) 86# USER_USERNAME is set by sre-wrapper from USER (spoofable by the user) 87use_sudo_user_for_username = False 88 89# Should projects be able to execute commands on the host (which might be useful) 90# shell : execution through subprocess.run with shell=True which allow pipes 91# split : execution through shlex.split to forbid pipes 92# False : no execution permitted on the host 93execute_commands_on_host: Literal["shell", "split", False] = "shell" 94 95 96authorized_src_dir = [main_sre_dir + '/lab', '/home'] 97 98 99################################################################################# 100# 101# Don't modify parameters below 102# 103################################################################################# 104 105sre_version = "0.9" 106default_docker_image_version = "1.28" 107sre_docker_namespace = "sysreseval" 108 109def sre_docker_image(image_name: str = "base") -> str: 110 return f"{sre_docker_namespace}/{image_name}:{default_docker_image_version}" 111 112default_docker_image = sre_docker_image() 113 114sre_wrapper = main_sre_dir + "/bin/sre-wrapper" 115sysreseval_exe = main_sre_dir + "/bin/sysreseval" 116sre_exe = main_sre_dir + "/sbin/sre" 117 118if debug_mode: 119 sre_wrapper = os.environ.get("SRE_WRAPPER", sre_wrapper) 120 sre_pub_dir = os.environ.get("SRE_PUB_DIR", sre_pub_dir) 121 122sre_projects_dir = sre_pub_dir + "/projects" 123 124 125 126 127archive_dirs = [sre_pub_dir + '/archives'] 128 129sre_name_env_variable = "SRE_LAB_NAME" 130sre_xauth_cookie_env_variable = "SRE_XAUTH_COOKIE" 131sre_host_ip_env_variable = "SRE_HOST_IP" 132 133sre_host_ip = "172.17.0.1" 134 135exetests_path = lib_dir + "/exetests.py" 136exetests_machines_path = '/usr/local/sbin/exetests.py' 137exetests_env_name = 'EXETESTS' 138exetests_separator = '@@@' 139default_timeout = 20 140 141exit_code_flavor_form_needed = 3 142exit_code_flavor_not_allowed = 4 143 144default_show_nat_network = True 145default_host_network_color = "deepskyblue" 146default_host_network_name = "Internet" 147default_host_network_shape = "hexagon" 148default_host_network_exploded = False 149default_host_network_edge_relative_length = 1.0 150 151default_machine_shape = "box" 152default_network_shape = "ellipse" 153 154pdf_schema_file = "schema.pdf" 155pdf_info_file = "informations.pdf" 156 157# External terminal emulator used to open machine connections. 158# The command is built as: terminal_cmd_prefix[:-1] + [terminal_title_opt, title] + terminal_cmd_prefix[-1:] + [sre_wrapper, "connect", project, machine] 159# xterm / xfce4-terminal use "-e" and "-title"; mate-terminal / gnome-terminal use "--" and "--title" 160# terminal_cmd_prefix = ["/usr/bin/xterm", "-e"] 161# terminal_title_opt = "-title" 162terminal_cmd_prefix = ["/usr/bin/mate-terminal", "--"] 163terminal_title_opt = "--title" 164 165# Terminal appearance defaults (overridable via ~/.config/sysreseval) 166terminal_font_size = 12 # font size in points 167terminal_color_scheme = "black_on_white" # "white_on_black" or "black_on_white" 168content_font_size = 12 # font size in points for information/questions views 169system_font_size = 10 # font size in points for menus, titles, labels 170 171graphicdir = main_sre_dir + "/graphics" 172thats_all_folks_svg = graphicdir + "/Thats_all_folks.svg" 173sysreseval_logo_svg = graphicdir + "/sysreseval.svg" 174machine_icon_svg_file = graphicdir + "/machine.svg" 175machine_forbidden_icon_svg_file = graphicdir + "/machine-forbidden.svg" 176switch_icon_svg_file = graphicdir + "/switch.svg" 177 178exam_only_affix = ["_EXAM_", "_OLD_", "_DRAFT_", "_TESTS_"] 179 180exam_json_name = "exam.json" 181exam_json_keyword = "exam_json" 182exam_start_after = "start_after" 183exam_end_before = "end_before" 184exam_eval_interval = "eval_interval" 185exam_pre_start_date = "pre_start_date" 186exam_started_at = "started_at" 187exam_ended_at = "ended_at" 188exam_duration = "duration" 189exam_labs = "labs" 190exam_record_sessions = "record_sessions" 191 192 193def parse_lab_entry(entry) -> tuple[str, str | None]: 194 """Parse a labs entry: new [lab, flavor] list or legacy plain string. 195 Returns (lab_cli_arg, flavor_name_or_None).""" 196 if isinstance(entry, list): 197 return entry[0], entry[1] 198 return entry, None # backward compat with old exam.json 199 200 201srelab_py_name = "srelab.py" 202titles_file_name = "titles.json" 203data_json_name = "data.json" 204private_dir_name = ".private" 205files_dir_name = "files" 206private_mount_dir_name = "mnt" 207user_public_dir_name = "user_public_dir" 208shared_dir_name = "shared" 209records_dir_name = "records" 210recorder_idle_time_limit = 10 # seconds; passed to asciinema --idle-time-limit to compress idle gaps in .cast files 211recorder_config_dir = sre_pub_dir + "/asciinema" # ASCIINEMA_CONFIG_HOME — avoids asciinema falling back to $HOME/.config (which the sre user cannot write) 212use_asciinema_for_records = True # True: prefer asciinema (fall back to script if missing). False: always record with script(1). 213eval_in_progress_name = "eval_in_progress" 214auto_eval_log_name = "auto_eval.log" 215debug_project_marker_name = "debug_project" 216auto_eval_count_keyword = "auto_eval_count" 217info_json_name = "info.json" 218 219answer_dir_name = "answers" 220answer_file_name = "answers.json" 221cheat_file_name = "cheat.json" 222 223hostname_keyword = "hostname" 224login_keyword = "login" 225fullname_keyword = "fullname" 226email_keyword = "email" 227language_keyword = "language" 228 229# Languages selectable in the GUI language priority dialog 230available_language_in_interface = ['en', 'fr'] 231language_display_names = {'en': 'English', 'fr': 'Français'} 232 233running_lab_name_keyword = "running_lab_name" 234eval_date_keyword = "eval_date" 235re_eval_date_keyword = "re_eval_date" 236 237sysreseval_answers_updated_at = 'answers_updated_at' 238sysreseval_exam_time_remaining = 'exam_time_remaining' 239sysreseval_exam_started_at = 'exam_started_at' 240sysreseval_exam_duration = 'exam_duration' 241sysreseval_exam_mode = 'exam_mode' 242 243initial_state_name = "initial" 244 245self_grade_timestamp_dir = sre_pub_dir + "/last_self_grades" 246 247svg_graph_name = "scheme.svg" 248 249srelab_link_name = "srelab" 250 251graphviz_default_nodesep = 0.8 252graphviz_default_ranksep = 1.5 253graphviz_default_splines = "curved" 254graphviz_default_overlap = "prism" 255 256 257class SRE: 258 args = None 259 module_rvlab = None 260 eval_obj = None 261 username = None 262 263 264def datetime_to_string(dt) -> str: 265 return f"{dt:%Y%m%d%H%M%S}" 266 267 268def string_to_datetime(string) -> datetime: 269 return datetime.strptime(string, "%Y%m%d%H%M%S") 270 271 272def get_lab_name_from_cli_arg(lab_cli_arg: str, is_path: bool) -> str: 273 if is_path: 274 lab_cli_arg = os.path.abspath(lab_cli_arg) 275 return lab_cli_arg.replace("/", "@") 276 277 278def get_running_lab_name(lab_name: str, instance_start_date: datetime, username: str = None) -> str: 279 if username is None: 280 if SRE.username is None: 281 username = "" 282 else: 283 username = SRE.username 284 if exetests_separator in lab_name: 285 raise ValueError(f"lab_name must not contain '{exetests_separator}': {lab_name!r}") 286 if exetests_separator in username: 287 raise ValueError(f"username must not contain '{exetests_separator}': {username!r}") 288 return f"{datetime_to_string(instance_start_date)}@@@{lab_name}@@@{username}" 289 290 291running_lab_name_match_pattern = '^([0-9]+)@@@(.+)@@@(.+)$' 292 293 294def get_lab_name_from_running_lab_name(running_lab_name: str) -> str: 295 match = re.match(running_lab_name_match_pattern, running_lab_name) 296 if match: 297 return match.group(2) 298 else: 299 return 'ERROR-running_lab_name-ILLEGAL-FORMAT' 300 301 302def get_username_from_running_lab_name(running_lab_name: str) -> str: 303 match = re.match(running_lab_name_match_pattern, running_lab_name) 304 if match: 305 return match.group(3) 306 return '' 307 308 309def get_abbreviated_lab_name_from_running_lab_name(running_lab_name: str) -> str: 310 name = get_lab_name_from_running_lab_name(running_lab_name).replace("@", "/").rpartition("/")[2] 311 return name.removesuffix(".py") 312 313 314def get_current_srelab_file_from_running_lab_name(running_lab_name: str) -> str: 315 lab_name = get_lab_name_from_running_lab_name(running_lab_name) 316 pathstr = lab_name.replace("@", "/") 317 if not pathstr.startswith("/"): 318 abspath = (Path(lab_dir) / pathstr).resolve() 319 else: 320 abspath = Path(pathstr).resolve() 321 if abspath.name.endswith(".py"): 322 return str(abspath) 323 else: 324 return str(abspath / srelab_py_name) 325 326 327def get_srelab_dir(running_lab_name: str): 328 lab_name = get_lab_name_from_running_lab_name(running_lab_name) 329 pathstr = lab_name.replace("@", "/") 330 if not pathstr.startswith("/"): 331 abspath = (Path(lab_dir) / pathstr).resolve() 332 else: 333 abspath = Path(pathstr).resolve() 334 if abspath.name.endswith(".py"): 335 return None 336 else: 337 return str(abspath) 338 339 340def project_has_directory(running_lab_name=None) -> bool: 341 if running_lab_name.endswith(".py"): 342 return False 343 else: 344 return True 345 346 347def get_archive_name(running_lab_name: str, date: datetime) -> str: 348 return f"{datetime_to_string(date)}_{running_lab_name}.zst" 349 350 351def private_lab_dir(running_lab_name: str) -> str: 352 return f"{sre_projects_dir}/{running_lab_name}/{private_dir_name}" 353 354 355def auto_eval_log_filename(running_lab_name: str) -> str: 356 return f"{private_lab_dir(running_lab_name)}/{auto_eval_log_name}" 357 358 359def debug_project_marker_filename(running_lab_name: str) -> str: 360 return f"{private_lab_dir(running_lab_name)}/{debug_project_marker_name}" 361 362 363def link_to_user_public_dir(running_lab_name: str) -> str: 364 return f"{sre_projects_dir}/{running_lab_name}/{private_dir_name}/{user_public_dir_name}" 365 366 367def public_lab_dir(running_lab_name: str) -> str: 368 return f"{sre_projects_dir}/{running_lab_name}" 369 370 371def info_filename(running_lab_name: str) -> str: 372 return f"{sre_projects_dir}/{running_lab_name}/{info_json_name}" 373 374 375def data_filename(running_lab_name: str) -> str: 376 return f"{sre_projects_dir}/{running_lab_name}/{private_dir_name}/{data_json_name}" 377 378 379def private_mount_dir(running_lab_name: str) -> str: 380 return f"{sre_projects_dir}/{running_lab_name}/{private_dir_name}/{private_mount_dir_name}" 381 382 383def files_dir(running_lab_name: str) -> str: 384 return f"{sre_projects_dir}/{running_lab_name}/{private_dir_name}/{files_dir_name}" 385 386 387def records_dir(running_lab_name: str) -> str: 388 return f"{sre_projects_dir}/{running_lab_name}/{private_dir_name}/{records_dir_name}" 389 390 391def srelab_link_filename(running_lab_name: str) -> str: 392 return f"{private_lab_dir(running_lab_name)}/{srelab_link_name}" 393 394 395def graph_filename(running_lab_name: str) -> str: 396 return f"{sre_projects_dir}/{running_lab_name}/{svg_graph_name}" 397 398 399def answers_filename(running_lab_name: str) -> str: 400 return f"{sre_projects_dir}/{running_lab_name}/{answer_dir_name}/{answer_file_name}" 401 402 403def cheat_filename(running_lab_name: str) -> str: 404 return f"{sre_projects_dir}/{running_lab_name}/{answer_dir_name}/{cheat_file_name}" 405 406 407def answers_dir(running_lab_name: str) -> str: 408 return f"{sre_projects_dir}/{running_lab_name}/{answer_dir_name}" 409 410 411def self_grade_timestamp_file(lab_name: str) -> str: 412 return f"{self_grade_timestamp_dir}/{lab_name}"
sre_uid =
1100
sre_gid =
1100
sre_user =
'sre'
docker_gid =
988
admin_uids =
[]
admin_gids =
[2000]
main_sre_dir =
$PWD
sre_pub_dir =
'/var/lib/sre'
sre_user_public_dir =
'/home/sre'
lab_dir =
'/Users/em/Orsay/Info/sre/lab'
lib_dir =
'/Users/em/Orsay/Info/sre/lib'
debug_mode =
True
email_in_gecos_last_field: bool =
True
max_docker_concurrency =
16
display_marks_in_auto_evaluations_by_default =
False
use_numerical_marks_by_default =
True
default_maximum_mark =
20
SELF_EVAL_SCOPE =
1
EXO_EVAL_SCOPE =
2
BOTH_EVAL_SCOPE =
3
grade_scopes =
(1, 2, 3)
default_eval_interval_during_exams =
60
default_eval_interval_without_exam_mode =
60
default_save_record_interval_during_exams =
60
default_exam_duration =
90
default_inactivity_threshold_in_watch_command =
90
default_dashboard_refresh_interval_in_watch_command =
1
max_duration_between_exam_pre_start_and_start =
60
default_exec_shell =
'/bin/bash'
allow_privileged_machines =
True
disable_volume_mount_on_root_partition =
False
use_sudo_user_for_username =
False
execute_commands_on_host: Literal['shell', 'split', False] =
'shell'
sre_version =
'0.9'
default_docker_image_version =
'1.28'
sre_docker_namespace =
'sysreseval'
def
sre_docker_image(image_name: str = 'base') -> str:
default_docker_image =
'sysreseval/base:1.28'
sre_wrapper =
'/Users/em/Orsay/Info/sre/bin/sre-wrapper'
sysreseval_exe =
'/Users/em/Orsay/Info/sre/bin/sysreseval'
sre_exe =
'/Users/em/Orsay/Info/sre/sbin/sre'
sre_projects_dir =
'/var/lib/sre/projects'
archive_dirs =
['/var/lib/sre/archives']
sre_name_env_variable =
'SRE_LAB_NAME'
sre_host_ip_env_variable =
'SRE_HOST_IP'
sre_host_ip =
'172.17.0.1'
exetests_path =
'/Users/em/Orsay/Info/sre/lib/exetests.py'
exetests_machines_path =
'/usr/local/sbin/exetests.py'
exetests_env_name =
'EXETESTS'
exetests_separator =
'@@@'
default_timeout =
20
exit_code_flavor_form_needed =
3
exit_code_flavor_not_allowed =
4
default_show_nat_network =
True
default_host_network_color =
'deepskyblue'
default_host_network_name =
'Internet'
default_host_network_shape =
'hexagon'
default_host_network_exploded =
False
default_host_network_edge_relative_length =
1.0
default_machine_shape =
'box'
default_network_shape =
'ellipse'
pdf_schema_file =
'schema.pdf'
pdf_info_file =
'informations.pdf'
terminal_cmd_prefix =
['/usr/bin/mate-terminal', '--']
terminal_title_opt =
'--title'
terminal_font_size =
12
terminal_color_scheme =
'black_on_white'
content_font_size =
12
system_font_size =
10
graphicdir =
'/Users/em/Orsay/Info/sre/graphics'
thats_all_folks_svg =
'/Users/em/Orsay/Info/sre/graphics/Thats_all_folks.svg'
sysreseval_logo_svg =
'/Users/em/Orsay/Info/sre/graphics/sysreseval.svg'
machine_icon_svg_file =
'/Users/em/Orsay/Info/sre/graphics/machine.svg'
machine_forbidden_icon_svg_file =
'/Users/em/Orsay/Info/sre/graphics/machine-forbidden.svg'
switch_icon_svg_file =
'/Users/em/Orsay/Info/sre/graphics/switch.svg'
exam_only_affix =
['_EXAM_', '_OLD_', '_DRAFT_', '_TESTS_']
exam_json_name =
'exam.json'
exam_json_keyword =
'exam_json'
exam_start_after =
'start_after'
exam_end_before =
'end_before'
exam_eval_interval =
'eval_interval'
exam_pre_start_date =
'pre_start_date'
exam_started_at =
'started_at'
exam_ended_at =
'ended_at'
exam_duration =
'duration'
exam_labs =
'labs'
exam_record_sessions =
'record_sessions'
def
parse_lab_entry(entry) -> tuple[str, str | None]:
194def parse_lab_entry(entry) -> tuple[str, str | None]: 195 """Parse a labs entry: new [lab, flavor] list or legacy plain string. 196 Returns (lab_cli_arg, flavor_name_or_None).""" 197 if isinstance(entry, list): 198 return entry[0], entry[1] 199 return entry, None # backward compat with old exam.json
Parse a labs entry: new [lab, flavor] list or legacy plain string. Returns (lab_cli_arg, flavor_name_or_None).
srelab_py_name =
'srelab.py'
titles_file_name =
'titles.json'
data_json_name =
'data.json'
private_dir_name =
'.private'
files_dir_name =
'files'
private_mount_dir_name =
'mnt'
user_public_dir_name =
'user_public_dir'
records_dir_name =
'records'
recorder_idle_time_limit =
10
recorder_config_dir =
'/var/lib/sre/asciinema'
use_asciinema_for_records =
True
eval_in_progress_name =
'eval_in_progress'
auto_eval_log_name =
'auto_eval.log'
debug_project_marker_name =
'debug_project'
auto_eval_count_keyword =
'auto_eval_count'
info_json_name =
'info.json'
answer_dir_name =
'answers'
answer_file_name =
'answers.json'
cheat_file_name =
'cheat.json'
hostname_keyword =
'hostname'
login_keyword =
'login'
fullname_keyword =
'fullname'
email_keyword =
'email'
language_keyword =
'language'
available_language_in_interface =
['en', 'fr']
language_display_names =
{'en': 'English', 'fr': 'Français'}
running_lab_name_keyword =
'running_lab_name'
eval_date_keyword =
'eval_date'
re_eval_date_keyword =
're_eval_date'
sysreseval_answers_updated_at =
'answers_updated_at'
sysreseval_exam_time_remaining =
'exam_time_remaining'
sysreseval_exam_started_at =
'exam_started_at'
sysreseval_exam_duration =
'exam_duration'
sysreseval_exam_mode =
'exam_mode'
initial_state_name =
'initial'
self_grade_timestamp_dir =
'/var/lib/sre/last_self_grades'
svg_graph_name =
'scheme.svg'
srelab_link_name =
'srelab'
graphviz_default_nodesep =
0.8
graphviz_default_ranksep =
1.5
graphviz_default_splines =
'curved'
graphviz_default_overlap =
'prism'
class
SRE:
def
datetime_to_string(dt) -> str:
def
string_to_datetime(string) -> datetime.datetime:
def
get_lab_name_from_cli_arg(lab_cli_arg: str, is_path: bool) -> str:
def
get_running_lab_name( lab_name: str, instance_start_date: datetime.datetime, username: str = None) -> str:
279def get_running_lab_name(lab_name: str, instance_start_date: datetime, username: str = None) -> str: 280 if username is None: 281 if SRE.username is None: 282 username = "" 283 else: 284 username = SRE.username 285 if exetests_separator in lab_name: 286 raise ValueError(f"lab_name must not contain '{exetests_separator}': {lab_name!r}") 287 if exetests_separator in username: 288 raise ValueError(f"username must not contain '{exetests_separator}': {username!r}") 289 return f"{datetime_to_string(instance_start_date)}@@@{lab_name}@@@{username}"
running_lab_name_match_pattern =
'^([0-9]+)@@@(.+)@@@(.+)$'
def
get_lab_name_from_running_lab_name(running_lab_name: str) -> str:
def
get_username_from_running_lab_name(running_lab_name: str) -> str:
def
get_abbreviated_lab_name_from_running_lab_name(running_lab_name: str) -> str:
def
get_current_srelab_file_from_running_lab_name(running_lab_name: str) -> str:
315def get_current_srelab_file_from_running_lab_name(running_lab_name: str) -> str: 316 lab_name = get_lab_name_from_running_lab_name(running_lab_name) 317 pathstr = lab_name.replace("@", "/") 318 if not pathstr.startswith("/"): 319 abspath = (Path(lab_dir) / pathstr).resolve() 320 else: 321 abspath = Path(pathstr).resolve() 322 if abspath.name.endswith(".py"): 323 return str(abspath) 324 else: 325 return str(abspath / srelab_py_name)
def
get_srelab_dir(running_lab_name: str):
328def get_srelab_dir(running_lab_name: str): 329 lab_name = get_lab_name_from_running_lab_name(running_lab_name) 330 pathstr = lab_name.replace("@", "/") 331 if not pathstr.startswith("/"): 332 abspath = (Path(lab_dir) / pathstr).resolve() 333 else: 334 abspath = Path(pathstr).resolve() 335 if abspath.name.endswith(".py"): 336 return None 337 else: 338 return str(abspath)
def
project_has_directory(running_lab_name=None) -> bool:
def
get_archive_name(running_lab_name: str, date: datetime.datetime) -> str:
def
private_lab_dir(running_lab_name: str) -> str:
def
auto_eval_log_filename(running_lab_name: str) -> str:
def
debug_project_marker_filename(running_lab_name: str) -> str:
def
link_to_user_public_dir(running_lab_name: str) -> str:
def
public_lab_dir(running_lab_name: str) -> str:
def
info_filename(running_lab_name: str) -> str:
def
data_filename(running_lab_name: str) -> str:
def
private_mount_dir(running_lab_name: str) -> str:
def
files_dir(running_lab_name: str) -> str:
def
records_dir(running_lab_name: str) -> str:
def
srelab_link_filename(running_lab_name: str) -> str:
def
graph_filename(running_lab_name: str) -> str:
def
answers_filename(running_lab_name: str) -> str:
def
cheat_filename(running_lab_name: str) -> str:
def
answers_dir(running_lab_name: str) -> str:
def
self_grade_timestamp_file(lab_name: str) -> str: