Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite CI to a Modern Standard #21

Merged
merged 15 commits into from
May 17, 2024
Merged
14 changes: 6 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ defaults:
jobs:
format:
runs-on: ubuntu-latest
env:
PYTHONUTF: 1

strategy:
matrix:
Expand All @@ -26,11 +28,7 @@ jobs:
python-version: ${{ matrix.python-version }}
cache: pipenv

- name: Install dependencies
run: |
set -e
pip install pipenv
pipenv install --dev --deploy

- name: Check formatting with format.sh
run: pipenv run ./scripts/format.sh && test -z "$(git status --porcelain=v1 .)"
# Only use this until pre-commit ci is added
# The README lists that as the recommended solution
- name: Check formatting and linting
uses: pre-commit/action@v3.0.1
57 changes: 49 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,50 @@
default_stages: [commit, push]
fail_fast: false
exclude: ^tin/(static/.*vendor|.*migrations)

repos:
- repo: local
hooks:
- id: format
name: format
entry: ./scripts/format.sh && test -z "$(git status --porcelain=v1 .)"
language: system
types: [python]
pass_filenames: false
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-ast
name: validate python
- id: trailing-whitespace
- id: mixed-line-ending
- id: check-toml
- id: check-yaml
- id: detect-private-key
- id: check-yaml
JasonGrace2282 marked this conversation as resolved.
Show resolved Hide resolved
- repo: https://github.com/codespell-project/codespell
rev: v2.2.5
hooks:
- id: codespell
files: ^.*\.(py|md|rst)$
args: ["-L", "num,ans", "-w"]
JasonGrace2282 marked this conversation as resolved.
Show resolved Hide resolved
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: python-check-blanket-noqa
- id: python-check-blanket-type-ignore
- id: python-use-type-annotations
name: No type comments
- id: python-no-log-warn
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.2
hooks:
- id: pyupgrade
name: Update code to new python versions
args: [--py38-plus]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.5
hooks:
- id: ruff
args: [ "--fix", "--exit-non-zero-on-fix" ]
files: ^tin/.*
name: ruff lint
- id: ruff-format
files: ^tin/.*
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
name: isort (python)
6 changes: 0 additions & 6 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ mosspy = "~=1.0"
django-debug-toolbar = "~=4.3"

[dev-packages]
flake8 = "*"
pylint = "*"
isort = "*"
black = "*"
autopep8 = "*"
pylint-django = "*"
django-stubs = "*"
pre-commit = "*"

Expand Down
572 changes: 197 additions & 375 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion create_debug_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
# fmt: on

for user_info in users:
print("Creating user {}".format(user_info[0]))
print(f"Creating user {user_info[0]}")
if user_info[1] is None:
user = User.objects.get_or_create(username=user_info[0])[0]
else:
Expand Down
61 changes: 61 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,64 @@ exclude = '''
)/
'''

[tool.ruff]
exclude = [
".eggs",
".git",
".github",
".hg",
".mypy_cache",
".tox",
".venv",
".env",
"venv",
"env",
"secret",
"build",
"_build",
"buck-out",
"dist",
"media",
"migrations",
]

# show fixes made in stdout
# show-fixes = true

line-length = 100

target-version = "py38"

[tool.ruff.lint]
select = [
# flake8-bugbear
"B",
# flake8-comprehensions
"C4",
# flake8-django
"DJ",
# pycodestyle
"E",
# Pyflakes
"F",
# flake8-no-pep420
"INP",
# Pylint
"PL",
# ruff
"RUF",
# pyupgrade
"UP",
]
ignore = [
# null=True on charfields
"DJ001",
# branching
"PLR09",
# mutable class attrs annotated as typing.ClassVar
"RUF012",
]

[tool.ruff.format]
docstring-code-format = true
line-ending = "lf"
3 changes: 3 additions & 0 deletions scripts/check.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/bin/bash
cd "$(dirname -- "$(dirname -- "$(readlink -f "$0")")")"

echo "This script is now deprecated, please use pre-commit instead."
echo "To run pre-commit before commiting, do 'pre-commit install'"

for cmd in flake8 isort pylint; do
if [[ ! -x "$(which "$cmd")" ]]; then
echo "Could not find $cmd. Please make sure that flake8, isort, and pylint are all installed."
Expand Down
3 changes: 3 additions & 0 deletions scripts/format.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/bin/bash
cd "$(dirname -- "$(dirname -- "$(readlink -f "$0")")")"

echo "This script is now deprecated, please use pre-commit instead"
echo "To run pre-commit before commiting, do 'pre-commit install'"

for cmd in black autopep8 isort; do
if [[ ! -x "$(which "$cmd")" ]]; then
echo "Could not find $cmd. Please make sure that black, autopep8, and isort are all installed."
Expand Down
20 changes: 10 additions & 10 deletions tin/apps/assignments/forms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from logging import getLogger
from typing import Dict, Iterable, Tuple
from typing import Iterable

from django import forms
from django.conf import settings
Expand Down Expand Up @@ -33,20 +33,20 @@ def __init__(self, course, *args, **kwargs):
# prevent description from getting too big
self.fields["description"].widget.attrs.update({"id": "description"})

def get_sections(self) -> Iterable[Dict[str, str | Tuple[str, ...] | bool]]:
def get_sections(self) -> Iterable[dict[str, str | tuple[str, ...] | bool]]:
for section in self.Meta.sections:
if section["name"]:
# operate on copy so errors on refresh don't happen
section = section.copy()
section["fields"] = tuple(self[field] for field in section["fields"])
yield section
new_section = section.copy()
new_section["fields"] = tuple(self[field] for field in new_section["fields"])
yield new_section

def get_main_section(self) -> Dict[str, str | Tuple[str, ...]]:
def get_main_section(self) -> dict[str, str | tuple[str, ...]]:
for section in self.Meta.sections:
if section["name"] == "":
section = section.copy()
section["fields"] = tuple(self[field] for field in section["fields"])
return section
new_section = section.copy()
new_section["fields"] = tuple(self[field] for field in new_section["fields"])
return new_section
logger.error(f"Could not find main section for assignment {self}")
return {"fields": ()}

Expand Down Expand Up @@ -127,7 +127,7 @@ class Meta:
"filename": "Clarify which file students need to upload (including the file "
"extension). For Java assignments, this also sets the name of the "
"saved submission file.",
"markdown": "This allows adding images, code blocks, or hyperlinks to the assignment description.",
"markdown": "This allows adding images, code blocks, or hyperlinks to the assignment description.", # noqa: E501
JasonGrace2282 marked this conversation as resolved.
Show resolved Hide resolved
"venv": "If set, Tin will run the student's code in this virtual environment.",
"grader_has_network_access": 'If unset, this effectively disables "Give submissions '
'internet access" below. If set, it increases the amount '
Expand Down
46 changes: 23 additions & 23 deletions tin/apps/assignments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ class Folder(models.Model):
def __str__(self):
return self.name

def __repr__(self):
return self.name

def get_absolute_url(self):
return reverse("assignments:show_folder", args=[self.course.id, self.id])

def __repr__(self):
return self.name


class AssignmentQuerySet(models.query.QuerySet):
def filter_visible(self, user):
Expand All @@ -54,14 +54,12 @@ def filter_editable(self, user):
def upload_grader_file_path(assignment, _): # pylint: disable=unused-argument
assert assignment.id is not None
if assignment.language == "P":
return "assignment-{}/grader.py".format(assignment.id)
return f"assignment-{assignment.id}/grader.py"
else:
return "assignment-{}/Grader.java".format(assignment.id)
return f"assignment-{assignment.id}/Grader.java"


class Assignment(models.Model):
objects = AssignmentQuerySet.as_manager()

name = models.CharField(max_length=50)
folder = models.ForeignKey(
Folder,
Expand Down Expand Up @@ -126,15 +124,17 @@ class Assignment(models.Model):

last_action_output = models.CharField(max_length=16 * 1024, default="", null=False, blank=True)

def __str__(self):
return self.name
objects = AssignmentQuerySet.as_manager()

def __repr__(self):
def __str__(self):
return self.name

def get_absolute_url(self):
return reverse("assignments:show", args=(self.id,))

def __repr__(self):
return self.name

def make_assignment_dir(self) -> None:
assignment_path = os.path.join(settings.MEDIA_ROOT, f"assignment-{self.id}")
os.makedirs(assignment_path, exist_ok=True)
Expand Down Expand Up @@ -169,7 +169,7 @@ def save_grader_file(self, grader_text: str) -> None:
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
encoding="utf-8",
universal_newlines=True,
text=True,
check=True,
)
except FileNotFoundError as e:
Expand Down Expand Up @@ -203,7 +203,7 @@ def list_files(self) -> List[Tuple[int, str, str, int, datetime.datetime]]:
def save_file(self, file_text: str, file_name: str) -> None:
self.make_assignment_dir()

fpath = os.path.join(settings.MEDIA_ROOT, "assignment-{}".format(self.id), file_name)
fpath = os.path.join(settings.MEDIA_ROOT, f"assignment-{self.id}", file_name)

os.makedirs(os.path.dirname(fpath), exist_ok=True)

Expand All @@ -220,7 +220,7 @@ def save_file(self, file_text: str, file_name: str) -> None:
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
encoding="utf-8",
universal_newlines=True,
text=True,
check=True,
)
except FileNotFoundError as e:
Expand Down Expand Up @@ -349,12 +349,12 @@ class Meta:
def __str__(self):
return f"Quiz for {self.assignment}"

def __repr__(self):
return f"Quiz for {self.assignment}"

def get_absolute_url(self):
return reverse("assignments:show", args=(self.assignment.id,))

def __repr__(self):
return f"Quiz for {self.assignment}"

def issues_for_student(self, student):
return (
sum(lm.severity for lm in self.log_messages.filter(student=student))
Expand Down Expand Up @@ -387,14 +387,14 @@ class LogMessage(models.Model):
def __str__(self):
return f"{self.content} for {self.quiz}"

def __repr__(self):
return f"{self.content} for {self.quiz}"

def get_absolute_url(self):
return reverse(
"assignments:student_submission", args=(self.quiz.assignment.id, self.student.id)
)

def __repr__(self):
return f"{self.content} for {self.quiz}"


def moss_base_file_path(obj, _): # pylint: disable=unused-argument
assert obj.assignment.id is not None
Expand Down Expand Up @@ -452,6 +452,9 @@ class MossResult(models.Model):
url = models.URLField(max_length=200, null=True, blank=True)
status = models.CharField(max_length=1024, default="", null=False, blank=True)

def __str__(self):
return f"Moss result for {self.assignment}"

@property
def extension(self):
return "java" if self.language == "java" else "py"
Expand All @@ -460,9 +463,6 @@ def extension(self):
def download_folder(self):
return os.path.join(settings.MEDIA_ROOT, "moss-runs", f"moss-{self.id}")

def __str__(self):
return f"Moss result for {self.assignment}"

def __repr__(self):
return f"Moss result for {self.assignment}"

Expand All @@ -476,7 +476,7 @@ def run_action(command: List[str]) -> str:
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding="utf-8",
universal_newlines=True,
text=True,
)
except FileNotFoundError as e:
logger.error("File not found: %s", e)
Expand Down
Loading
Loading