Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/ruff.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Ruff

on:
pull_request:
push:
branches:
- master

permissions:
contents: read

jobs:
ruff:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v6
- name: Install dependencies
run: uv sync --group dev
- name: Ruff check
run: uv run ruff check . --format=github
6 changes: 3 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from pathlib import Path
import shutil
import asyncio
import argparse
import asyncio
import logging
import shutil
from pathlib import Path

from src import make_curriculum, make_rss

Expand Down
27 changes: 27 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,34 @@ dev = [
"jupyter>=1.1.1",
"notebook>=7.4.7",
"pandas>=2.3.3",
"ruff>=0.14.14",
]

[project.scripts]
static-build = "main:main"

[tool.ruff]
target-version = "py313"
line-length = 88
extend-exclude = ["build", "static"]
lint.select = [
"B",
"E",
"F",
"I",
"N",
"SIM",
"UP",
]

[tool.ruff.lint.per-file-ignores]
"src/models/*.py" = ["N815"]
"src/utils/catalog.py" = ["N806"]
"src/utils/jw.py" = ["N802", "N806", "N816"]
"src/utils/tj_rss.py" = ["N802"]
"tools/bus_data_gen.py" = ["N816"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"
10 changes: 5 additions & 5 deletions src/curriculum.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import asyncio
import logging
from pathlib import Path

from tqdm import tqdm

from .models.course import Course
from .utils.catalog import get_semesters, get_courses, get_exams
from .utils.auth import RequestSession, USTCSession
from .utils.catalog import get_courses, get_exams, get_semesters
from .utils.jw import update_lectures
from .utils.tools import save_json, BUILD_DIR
from .utils.auth import USTCSession, RequestSession
from .utils.tools import BUILD_DIR, save_json


async def fetch_semester(
Expand Down Expand Up @@ -36,7 +36,7 @@ async def fetch_semester(
exams = {}

for course in incomplete_courses:
if course.id in exams.keys():
if course.id in exams:
course.exams = exams[course.id]

sem = asyncio.Semaphore(50)
Expand Down
8 changes: 4 additions & 4 deletions src/models/course.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import Optional

from pydantic import BaseModel

from .lecture import Lecture
from .exam import Exam
from .lecture import Lecture


class Course(BaseModel):
Expand All @@ -13,8 +13,8 @@ class Course(BaseModel):
teacherName: str
lectures: list[Lecture]
exams: list[Exam]
dateTimePlacePersonText: Optional[str]
courseType: Optional[str]
dateTimePlacePersonText: str | None
courseType: str | None
courseGradation: str
courseCategory: str
educationType: str
Expand Down
4 changes: 2 additions & 2 deletions src/models/exam.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

from pydantic import BaseModel
from typing import Optional


class Exam(BaseModel):
Expand All @@ -10,5 +10,5 @@ class Exam(BaseModel):
examType: str
startHHMM: int
endHHMM: int
examMode: Optional[str]
examMode: str | None
additionalInfo: dict[str, str]
5 changes: 4 additions & 1 deletion src/models/semester.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime

from pydantic import BaseModel

from .course import Course
Expand All @@ -12,7 +13,9 @@ class Semester(BaseModel):
endDate: int

def __str__(self) -> str:
date_fmt = lambda ts: datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d")
def date_fmt(ts: int) -> str:
return datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d")

start_date = date_fmt(self.startDate)
end_date = date_fmt(self.endDate)
return f"Semester(id={self.id}, name={self.name}, {start_date} - {end_date})"
11 changes: 6 additions & 5 deletions src/rss.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import datetime
from pathlib import Path
from typing import cast

import feedgenerator
import feedparser
import html2text
import yaml
from pathlib import Path
from tqdm import tqdm
import feedgenerator
import datetime
from typing import cast

from .utils.tj_rss import tj_ustc_RSS
from .utils.tools import BUILD_DIR, RSS_CONFIG_PATH
Expand Down Expand Up @@ -58,7 +59,7 @@ async def get_and_clean_feed(url: str, path_to_save: Path):


async def make_rss() -> None:
with open(RSS_CONFIG_PATH, "r") as f:
with open(RSS_CONFIG_PATH) as f:
config = yaml.safe_load(f)

rss_path = BUILD_DIR / "rss"
Expand Down
29 changes: 15 additions & 14 deletions src/utils/auth.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import os
import logging
from typing import Optional, Any, Dict
from pathlib import Path
import os
from contextlib import suppress

from pyotp import TOTP, parse_uri
from enum import Enum
from pathlib import Path
from typing import Any

from dotenv import load_dotenv
from patchright.async_api import (
async_playwright,
Page,
Browser,
BrowserContext,
Page,
Playwright,
async_playwright,
)
from pyotp import TOTP, parse_uri

from .tools import save_json, cache_dir_from_url
from .tools import cache_dir_from_url, save_json


class RequestSession:
Expand All @@ -33,7 +33,7 @@ def __init__(
self.logger = logging.getLogger(__name__)

async def get(self, url: str, **kwargs: Any):
params: Dict[str, Any] = {
params: dict[str, Any] = {
"url": url,
"timeout": kwargs.get("timeout", self.timeout_ms),
"fail_on_status_code": kwargs.get(
Expand All @@ -49,7 +49,7 @@ async def get(self, url: str, **kwargs: Any):
return r

async def post(self, url: str, data: Any = None, **kwargs: Any):
params: Dict[str, Any] = {
params: dict[str, Any] = {
"url": url,
"data": data,
"timeout": kwargs.get("timeout", self.timeout_ms),
Expand Down Expand Up @@ -87,14 +87,14 @@ async def post_json(self, url: str, data: Any = None, **kwargs: Any):
class USTCSession:
"""Context manager for USTC authentication and returns a RequestSession"""

def __init__(self, headless: bool = True, proxy: Optional[dict] = None):
def __init__(self, headless: bool = True, proxy: dict | None = None):
self.headless = headless
self.proxy = proxy
self.playwright: Playwright
self.browser: Browser
self.context: BrowserContext
self.page: Page
self.totp: Optional[TOTP] = None
self.totp: TOTP | None = None
self.logger = logging.getLogger(__name__)

load_dotenv()
Expand All @@ -103,7 +103,8 @@ def __init__(self, headless: bool = True, proxy: Optional[dict] = None):
self.password = os.getenv("USTC_PASSPORT_PASSWORD", "")
if self.username == "" or self.password == "":
raise ValueError(
"USTC_PASSPORT_USERNAME and USTC_PASSPORT_PASSWORD must be set in environment variables"
"USTC_PASSPORT_USERNAME and USTC_PASSPORT_PASSWORD must be set in "
"environment variables"
)
self.totp_url = os.getenv("USTC_PASSPORT_TOTP_URL", "")

Expand Down Expand Up @@ -251,7 +252,7 @@ async def detect_login_state() -> LoginState:
self.logger.error(f"login error={e}")
return False

self.logger.info(f"login failed")
self.logger.info("login failed")
return False

async def _after_login(self):
Expand Down
6 changes: 3 additions & 3 deletions src/utils/catalog.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import asyncio

from ..models import Semester, Course, Exam
from ..models import Course, Exam, Semester
from .auth import RequestSession
from .tools import (
raw_date_to_unix_timestamp,
compose_start_end,
join_nonempty,
raw_date_to_unix_timestamp,
)
from .auth import RequestSession


async def get_semesters(session: RequestSession) -> list[Semester]:
Expand Down
7 changes: 3 additions & 4 deletions src/utils/jw.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import logging

from ..models import Course, Lecture
from .auth import RequestSession
from .tools import (
cache_dir_from_url,
compose_start_end,
save_json,
)
from .auth import RequestSession


indexStartTimes: dict[int, int] = {
1: 7 * 60 + 50,
Expand Down Expand Up @@ -55,9 +54,9 @@ def cleanLectures(lectures: list[Lecture]) -> list[Lecture]:
for lecture in lectures:
for r in result:
if lecture.startDate >= r.startDate and lecture.endDate <= r.endDate:
if not lecture.teacherName in r.teacherName:
if lecture.teacherName not in r.teacherName:
r.teacherName += "," + lecture.teacherName
if not lecture.location in r.location:
if lecture.location not in r.location:
r.location += "," + lecture.location
break
elif lecture.endDate == r.startDate:
Expand Down
12 changes: 9 additions & 3 deletions src/utils/tj_rss.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@
description = "体育教学中心,http://www.tj.ustc.edu.cn"

headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57",
"Accept": (
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,"
"image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
),
"user-agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57"
),
}


Expand Down Expand Up @@ -79,7 +85,7 @@ def tj_ustc_RSS(output_dir: Path | str):
fg.description(description)
fg.link(href="http://www.tj.ustc.edu.cn/tzgg/list.htm", rel="alternate")
fg.language("zh-CN")
fg.lastBuildDate(datetime.datetime.utcnow().astimezone(tz=datetime.timezone.utc))
fg.lastBuildDate(datetime.datetime.utcnow().astimezone(tz=datetime.UTC))
fg.ttl(5)

for item in items:
Expand Down
10 changes: 6 additions & 4 deletions src/utils/tools.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from collections.abc import Iterable
from datetime import datetime
from json import dump
from pathlib import Path
from typing import Any
from urllib.parse import urlparse

from pydantic.json import pydantic_encoder
from pytz import timezone
from typing import Any, Iterable, Tuple
from urllib.parse import urlparse

tz = timezone("Asia/Shanghai")

Expand All @@ -29,7 +31,7 @@ def save_json(obj: Any, path: Path) -> None:
dump(obj, f, default=pydantic_encoder, ensure_ascii=False)


def compose_start_end(date_str: str, start_hhmm: int, end_hhmm: int) -> Tuple[int, int]:
def compose_start_end(date_str: str, start_hhmm: int, end_hhmm: int) -> tuple[int, int]:
def compose_datetime(date_str: str, hhmm: int) -> int:
base = raw_date_to_unix_timestamp(date_str)
return base + int(hhmm // 100) * 3600 + int(hhmm % 100) * 60
Expand All @@ -48,7 +50,7 @@ def cache_dir_from_url(url: str) -> Path:
"jw.ustc.edu.cn": "jw",
}

if host in host_abbrs.keys():
if host in host_abbrs:
host = host_abbrs[host]

return Path("build") / "cache" / host / path
Expand Down
8 changes: 4 additions & 4 deletions tools/bus_data_gen.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Optional

import jsonpickle


Expand Down Expand Up @@ -34,14 +34,14 @@ def __init__(self, id: int, campuses: list[Campus]):


class RouteSchedule:
def __init__(self, id: int, route: Route, time: list[list[Optional[str]]]):
def __init__(self, id: int, route: Route, time: list[list[str | None]]):
self.id = id
self.route = route
self.time = time

id: int
route: Route
time: list[list[Optional[str]]]
time: list[list[str | None]]


class RouteScheduleP:
Expand Down Expand Up @@ -311,7 +311,7 @@ def __init__(
)


def generate_bus_data(output_path: Optional[Path] = None) -> Path:
def generate_bus_data(output_path: Path | None = None) -> Path:
data_json = jsonpickle.encode(data, unpicklable=False)
if output_path is None:
output_path = (
Expand Down
Loading
Loading