Skip to content

Commit

Permalink
Honestly, lots of breaking changes
Browse files Browse the repository at this point in the history
- Overall, finalizing migration of banner generation to Hugo
- Restructured template directories
- Fleshing out pathing to make use of this, rather than `actions/paths`
- Adding syllabus initialization and sorting
- Preliminary file renaming
- Preparing to migrate to `inv[oke]` to make task enumeration more
  obvious (especially since these are documented via GitHub Actions)
- Ironed out some file-writing issues with `nbconvert`
- Correct kaggle notebook diff to properly compare notebooks in a
  similar way (had to re-render their JSON before sha256)
  • Loading branch information
John M committed Jan 29, 2020
1 parent dcac1c3 commit c1f6618
Show file tree
Hide file tree
Showing 21 changed files with 347 additions and 196 deletions.
85 changes: 50 additions & 35 deletions autobot/actions/meetings.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,18 @@
import io
import os
import copy
import datetime
import shutil
import subprocess
import warnings
from pathlib import Path
from hashlib import sha256
from typing import List, Dict
from itertools import product
from distutils.dir_util import copy_tree
from autobot import load_upkeep_template

import imgkit
import json

import requests
import yaml
from PIL import Image
import pandas as pd
import numpy as np
from jinja2 import Template
import nbformat as nbf
import nbconvert as nbc
from nbgrader.preprocessors import ClearSolutions, ClearOutput

from autobot import get_template
from autobot.concepts import Meeting
from tqdm import tqdm

from autobot.actions import paths
from autobot.apis import kaggle
from autobot.apis.nbconvert import (
SolutionbookToPostExporter,
SolutionbookToWorkbookExporter,
TemplateNotebookValidator,
ValidateNBGraderPreprocessor,
FileExtensions,
)
from autobot.concepts import Meeting
from autobot.pathing import templates, repositories


def update_or_create_folders_and_files(meeting: Meeting):
Expand All @@ -58,7 +37,37 @@ def update_or_create_folders_and_files(meeting: Meeting):
# TODO allow for meetings to be moved – this intuitively makes sense to resolve
# with the `filename` parameter, but may also need to consider the `date` (you
# can get all this from `repr(meeting)`)
raise NotImplementedError()
path = repositories.local_meeting_root(meeting)

root = repositories.local_semester_root(meeting)

local_neighbors = list(root.iterdir())

for d in local_neighbors:
date, name = d.stem[:10], d.stem[11:]

# remove placeholder
# rename by date
shares_meet = ("meeting" in name and int(name[-2:]) == meeting.number - 1)
shares_date = (date == repr(meeting)[:10])
if d.stem != repr(meeting) and (shares_meet or shares_date):
old_path = d
old_soln = d / "".join([d.stem, FileExtensions.Solutionbook])

new_path = old_path.with_name(repr(meeting))
new_soln = old_path / "".join([repr(meeting), FileExtensions.Solutionbook])

# NOTE: rename internal files before renaming the folder
tqdm.write(f"renaming: {old_soln} -> {new_soln}")
old_soln.rename(new_soln)
tqdm.write(f"renaming: {old_path} -> {new_path}")
old_path.rename(new_path)
return
# TODO: handle meeting swaps (e.g. meeting02 with meeting 06, etc.)
# TODO: handle meeting renames with swaps
# TODO: allow for renaming to propagate through to platforms like Kaggle

path.mkdir(exist_ok=True, parents=True)


""" Use the following as "inspiration", this feels like it's far too complex to understand
Expand Down Expand Up @@ -141,18 +150,24 @@ def update_or_create_notebook(meeting: Meeting, overwrite: bool = False):
validator = TemplateNotebookValidator()
_, _ = validator.from_meeting(meeting)

kernel_metadata = load_upkeep_template("meetings/kernel-metadata.json.j2")
kernel_metadata = templates.load("meeting/kernel-metadata.json.j2")

with open(paths.repo_meeting_folder(meeting) / "kernel-metadata.json", "w") as f:
meeting.optional["kaggle"]["competitions"].insert(
0, kaggle.slug_competition(meeting)
)
text = kernel_metadata.render(
meeting.optional["kaggle"]["competitions"].insert(
0, kaggle.slug_competition(meeting)
)

# TODO get this sorted into proper JSON and templating
text = (
kernel_metadata.render(
slug=kaggle.slug_kernel(meeting),
notebook=repr(meeting),
kaggle=meeting.optional["kaggle"],
)
f.write(text.replace("'", '"')) # JSON doesn't like single-quotes
.replace("'", '"') # JSON doesn't like single-quoted strings
.lower()
)

open(paths.repo_meeting_folder(meeting) / "kernel-metadata.json", "w").write(text)

# Converts a "Solutionbook" (`.solution.ipynb`) to a "Workbook" (`.ipynb`).
# Makes use of Preprocessors and FileWriters to place generate and write the
Expand Down
2 changes: 1 addition & 1 deletion autobot/actions/paths.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
import os
from pathlib import Path

from autobot import ORG_NAME
from autobot.concepts import Group, Meeting
Expand Down
10 changes: 5 additions & 5 deletions autobot/actions/reader.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import os
import re
import signal
import sys
from datetime import datetime
from time import sleep
from getpass import getpass
import signal
from pathlib import Path
from time import sleep

from termcolor import colored, cprint

import requests
import pandas as pd
import requests

from termcolor import colored, cprint

__author__ = "John Muchovej"

Expand Down
157 changes: 109 additions & 48 deletions autobot/actions/syllabus.py
Original file line number Diff line number Diff line change
@@ -1,74 +1,135 @@
import yaml

from tqdm import tqdm

from autobot.apis import ucf
from autobot.pathing import templates, repositories
from autobot.concepts import Coordinator, Group, Meeting

from . import paths

try:
from yaml import CLoader as Loader, CDumper as Dumper
except ImportError:
from yaml import Loader, Dumper

from autobot.concepts import Group, Coordinator, Meeting
from autobot.apis import ucf
import copy

from . import paths

"""Expected `syllabus.yml` format. You can also find this in `templates/seed/group/syllabus.yml`.
```yaml
- required:
title: ""
cover: ""
filename: ""
instructors: []
description: >-
optional:
date: ""
room: ""
tags: []
papers: []
urls:
slides: ""
youtube: ""
kaggle:
datasets: []
competitions: []
kernels: []
gpu: false
```
"""
# TODO migrate from PyYAML to Confuse: https://github.com/beetbox/confuse


def init(group: Group):
path = repositories.local_semester_root(group)

assert not (path / "syllabus.yml").exists(), "found: syllabus.yml"
assert (path / "overhead.yml").exists(), "found: overhead.yml"

syllabus = yaml.load(open(templates.get("group/syllabus.yml"), "r"), Loader=Loader)
overhead = yaml.load(open(path / "overhead.yml", "r"), Loader=Loader)["meetings"]

# assert (
# overhead["start_offset"] >= 0
# ), "Need to know the week we start this group to do anything else..."

try:
schedule = ucf.make_schedule(group, overhead)
except AssertionError:
# don't require `overhead` to be properly filled out
schedule = [None] * (
ucf.SEMESTER_LEN[group.semester.name.lower()] - overhead["start_offset"]
)
finally:
meetings = {}
for idx, meeting in tqdm(enumerate(schedule), desc="Initial Meeting Setup"):
# TODO add support for non-standard meeting times
if hasattr(meeting, "date"):
syllabus["optional"]["date"] = meeting.date.isoformat()

if hasattr(meeting, "room"):
syllabus["optional"]["room"] = meeting.room

meetings[f"meeting{idx:02d}"] = copy.deepcopy(syllabus)

yaml.dump(
meetings,
stream=open(path / "syllabus.yml", "w"),
Dumper=Dumper,
width=80,
sort_keys=False,
# default_style='"',
)


def sort(group: Group):
path = repositories.local_semester_root(group)

assert (path / "syllabus.yml").exists()
assert (path / "overhead.yml").exists()

overhead = yaml.load(open(path / "overhead.yml", "r"), Loader=Loader)["meetings"]
schedule = ucf.make_schedule(group, overhead)

syllabus_old = yaml.load(open(path / "syllabus.yml", "r"), Loader=Loader)
syllabus_new = {}

# resort entries
for idx, previous in tqdm(
enumerate(syllabus_old.values()), desc="Resorting Syllabus"
):
syllabus_new[f"meeting{idx:02d}"] = copy.deepcopy(previous)

# re-date the entries
for meeting, info in tqdm(zip(syllabus_new.keys(), schedule), desc="Update Dates"):
syllabus_new[meeting]["optional"]["date"] = info.date.isoformat()

yaml.dump(
syllabus_new,
stream=open(path / "syllabus.yml", "w"),
Dumper=Dumper,
width=80,
sort_keys=False,
# default_style='"',
)


def parse(group: Group):
path = repositories.local_semester_root(group)

# region 1. Read `overhead.yml` and seed Coordinators
overhead_yml = paths.repo_group_folder(group) / "overhead.yml"
overhead_yml = yaml.load(open(overhead_yml, "r"), Loader=Loader)
coordinators = overhead_yml["coordinators"]
setattr(group, "coords", Coordinator.parse_yaml(coordinators))
overhead = yaml.load(open(path / "overhead.yml", "r"), Loader=Loader)
setattr(group, "coords", Coordinator.parse_yaml(overhead))

meeting_overhead = overhead_yml["meetings"]
meeting_schedule = ucf.make_schedule(group, meeting_overhead)
# TODO validate dates follow the meeting pattern and ping Discord if not
overhead = overhead["meetings"]
schedule = ucf.make_schedule(group, overhead)
# endregion

# region 2. Read `syllabus.yml` and parse Syllabus
syllabus_yml = paths.repo_group_folder(group) / "syllabus.yml"
syllabus_yml = yaml.load(open(syllabus_yml, "r"), Loader=Loader)
syllabus = yaml.load(open(path / "syllabus.yml", "r"), Loader=Loader)

syllabus = []
meetings = []
# TODO support undecided filenames
for meeting, schedule in tqdm(
zip(syllabus_yml, meeting_schedule), desc="Parsing Meetings"
for (key, meeting), when_where in tqdm(
zip(syllabus.items(), schedule), desc="Parsing Meetings"
):
try:
syllabus.append(Meeting(group, meeting, schedule))
except AssertionError:
tqdm.write(
f"You're missing `required` fields from the meeting happening on {schedule.date} in {schedule.room}"
)
continue
# implicitly trust `syllabus.yml` to be correct
if not meeting["optional"].get("room", False):
meeting["optional"]["room"] = when_where.room

if not meeting["optional"].get("date", False):
meeting["optional"]["date"] = when_when.date.isoformat()

meetings.append(Meeting(group, meeting, tmpname=key))
# try:
# meetings.append(Meeting(group, meeting, key))
# except AssertionError:
# tqdm.write(
# f"You're missing `required` fields from the meeting happening on {meeting.date} in {schedule.room}"
# )
# continue
# endregion

return syllabus
return meetings


def write(group: Group):
Expand Down
4 changes: 2 additions & 2 deletions autobot/apis/github.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from github.GithubException import GithubException
from github.MainClass import Github
from github.NamedUser import NamedUser as User
from github.Organization import Organization as Org
from github.Team import Team
from github.MainClass import Github
from github.GithubException import GithubException

from autobot import ORG_NAME
from autobot.concepts import Group
Expand Down
1 change: 0 additions & 1 deletion autobot/apis/hugo.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@

Loading

0 comments on commit c1f6618

Please sign in to comment.